From cd220551d3fa0a8db3c30b2e3b09ebd5f223577f Mon Sep 17 00:00:00 2001 From: Mark Beacom Date: Tue, 9 Oct 2018 09:21:21 -0400 Subject: [PATCH] General grants cleanup, mig squash, trans in templates, and BE lint --- app/grants/admin.py | 3 - app/grants/apps.py | 22 + app/grants/migrations/0001_initial.py | 59 +- .../migrations/0002_auto_20180918_1844.py | 68 --- .../migrations/0003_auto_20180918_2234.py | 18 - .../migrations/0004_auto_20180918_2322.py | 18 - .../migrations/0005_auto_20180918_2328.py | 18 - .../migrations/0006_grant_tokenaddress.py | 18 - .../migrations/0007_auto_20180920_2115.py | 57 -- .../migrations/0008_auto_20180927_1537.py | 43 -- .../migrations/0009_auto_20180927_1544.py | 23 - .../migrations/0010_auto_20180927_1643.py | 63 -- .../0011_grant_required_gas_price.py | 18 - .../migrations/0012_auto_20181001_0010.py | 28 - app/grants/models.py | 53 +- app/grants/templates/grants/cancel.html | 180 +++--- app/grants/templates/grants/fund.html | 261 +++++---- app/grants/templates/grants/index.html | 23 +- app/grants/templates/grants/new.html | 55 +- app/grants/templates/grants/show.html | 32 +- app/grants/tests.py | 3 - app/grants/urls.py | 7 +- app/grants/utils.py | 548 ------------------ app/grants/views.py | 75 +-- 24 files changed, 447 insertions(+), 1246 deletions(-) delete mode 100644 app/grants/admin.py delete mode 100644 app/grants/migrations/0002_auto_20180918_1844.py delete mode 100644 app/grants/migrations/0003_auto_20180918_2234.py delete mode 100644 app/grants/migrations/0004_auto_20180918_2322.py delete mode 100644 app/grants/migrations/0005_auto_20180918_2328.py delete mode 100644 app/grants/migrations/0006_grant_tokenaddress.py delete mode 100644 app/grants/migrations/0007_auto_20180920_2115.py delete mode 100644 app/grants/migrations/0008_auto_20180927_1537.py delete mode 100644 app/grants/migrations/0009_auto_20180927_1544.py delete mode 100644 app/grants/migrations/0010_auto_20180927_1643.py delete mode 100644 app/grants/migrations/0011_grant_required_gas_price.py delete mode 100644 app/grants/migrations/0012_auto_20181001_0010.py delete mode 100644 app/grants/tests.py delete mode 100644 app/grants/utils.py diff --git a/app/grants/admin.py b/app/grants/admin.py deleted file mode 100644 index 8c38f3f3dad..00000000000 --- a/app/grants/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/app/grants/apps.py b/app/grants/apps.py index bc21e364102..1c3e315af52 100644 --- a/app/grants/apps.py +++ b/app/grants/apps.py @@ -1,5 +1,27 @@ +# -*- coding: utf-8 -*- +"""Define the Grants application configuration. + +Copyright (C) 2018 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 django.apps import AppConfig class GrantsConfig(AppConfig): + """Define the Grants application configuration.""" + name = 'grants' + verbose_name = 'Grants' diff --git a/app/grants/migrations/0001_initial.py b/app/grants/migrations/0001_initial.py index 42f200492a7..57dff678217 100644 --- a/app/grants/migrations/0001_initial.py +++ b/app/grants/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.1 on 2018-09-17 21:37 +# Generated by Django 2.1.1 on 2018-10-09 13:20 from django.db import migrations, models import django.db.models.deletion @@ -14,32 +14,69 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='Contribution', + 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)), + ('tx_id', models.CharField(default='0x0', max_length=255)), + ], + options={ + 'abstract': False, + }, + ), migrations.CreateModel( name='Grant', 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)), - ('pitch', models.CharField(default='', max_length=255)), + ('status', models.BooleanField(default=True)), + ('title', models.CharField(default='', max_length=255)), ('description', models.TextField(blank=True, default='')), ('reference_url', models.URLField(db_index=True)), - ('current_funding', models.DecimalField(decimal_places=4, default=0, max_digits=50)), - ('goal_funding', models.DecimalField(decimal_places=4, default=0, max_digits=50)), - ('profile', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='grants', to='dashboard.Profile')), + ('image_url', models.URLField(default='')), + ('admin_address', models.CharField(default='0x0', max_length=255)), + ('frequency', models.DecimalField(decimal_places=0, default=30, max_digits=50)), + ('amount_goal', models.DecimalField(decimal_places=4, default=1, max_digits=50)), + ('amount_received', models.DecimalField(decimal_places=4, default=0, max_digits=50)), + ('token_address', models.CharField(default='0x0', max_length=255)), + ('contract_address', models.CharField(default='0x0', max_length=255)), + ('transaction_hash', models.CharField(default='0x0', max_length=255)), + ('network', models.CharField(default='0x0', max_length=255)), + ('required_gas_price', models.DecimalField(decimal_places=0, default='0', max_digits=50)), + ('admin_profile', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='grant_admin', to='dashboard.Profile')), + ('team_member_profiles', models.ManyToManyField(related_name='grant_team_members', to='dashboard.Profile')), ], options={ 'abstract': False, }, ), migrations.CreateModel( - name='Stakeholder', + name='Subscription', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('eth_address', models.CharField(max_length=50)), - ('name', models.CharField(blank=True, max_length=255)), - ('role', models.CharField(blank=True, max_length=255)), - ('url', models.URLField(db_index=True)), + ('created_on', models.DateTimeField(db_index=True, default=economy.models.get_time)), + ('modified_on', models.DateTimeField(default=economy.models.get_time)), + ('status', models.BooleanField(default=True)), + ('subscription_hash', models.CharField(default='', max_length=255)), + ('contributor_signature', models.CharField(default='', max_length=255)), + ('contributor_address', models.CharField(default='', max_length=255)), + ('amount_per_period', models.DecimalField(decimal_places=4, default=1, max_digits=50)), + ('token_address', models.CharField(default='0x0', max_length=255)), + ('gas_price', models.DecimalField(decimal_places=4, default=1, max_digits=50)), + ('network', models.CharField(default='0x0', max_length=255)), + ('contributor_profile', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='grant_contributor', to='dashboard.Profile')), + ('grant', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subscriptions', to='grants.Grant')), ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='contribution', + name='subscription', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subscription_contribution', to='grants.Subscription'), ), ] diff --git a/app/grants/migrations/0002_auto_20180918_1844.py b/app/grants/migrations/0002_auto_20180918_1844.py deleted file mode 100644 index a2ac912cda5..00000000000 --- a/app/grants/migrations/0002_auto_20180918_1844.py +++ /dev/null @@ -1,68 +0,0 @@ -# Generated by Django 2.1.1 on 2018-09-18 18:44 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dashboard', '0108_auto_20180917_2137'), - ('grants', '0001_initial'), - ] - - operations = [ - migrations.RenameField( - model_name='grant', - old_name='current_funding', - new_name='amountGoal', - ), - migrations.RenameField( - model_name='grant', - old_name='goal_funding', - new_name='amountReceived', - ), - migrations.RemoveField( - model_name='grant', - name='pitch', - ), - migrations.RemoveField( - model_name='grant', - name='profile', - ), - migrations.AddField( - model_name='grant', - name='adminAddress', - field=models.CharField(default='0x0', max_length=255), - ), - migrations.AddField( - model_name='grant', - name='adminProfile', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='grant_admin', to='dashboard.Profile'), - ), - migrations.AddField( - model_name='grant', - name='frequency', - field=models.DecimalField(decimal_places=0, default=0, max_digits=50), - ), - migrations.AddField( - model_name='grant', - name='image_url', - field=models.URLField(default=''), - ), - migrations.AddField( - model_name='grant', - name='status', - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name='grant', - name='teamMemberProfiles', - field=models.ManyToManyField(related_name='grant_team_members', to='dashboard.Profile'), - ), - migrations.AlterField( - model_name='grant', - name='title', - field=models.CharField(default='Very grant. Much cool.', max_length=255), - ), - ] diff --git a/app/grants/migrations/0003_auto_20180918_2234.py b/app/grants/migrations/0003_auto_20180918_2234.py deleted file mode 100644 index 7cb3590834e..00000000000 --- a/app/grants/migrations/0003_auto_20180918_2234.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.1 on 2018-09-18 22:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('grants', '0002_auto_20180918_1844'), - ] - - operations = [ - migrations.AlterField( - model_name='grant', - name='title', - field=models.CharField(default='', max_length=255), - ), - ] diff --git a/app/grants/migrations/0004_auto_20180918_2322.py b/app/grants/migrations/0004_auto_20180918_2322.py deleted file mode 100644 index d30a54ca0e9..00000000000 --- a/app/grants/migrations/0004_auto_20180918_2322.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.1 on 2018-09-18 23:22 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('grants', '0003_auto_20180918_2234'), - ] - - operations = [ - migrations.AlterField( - model_name='grant', - name='amountGoal', - field=models.DecimalField(decimal_places=4, default=1, max_digits=50), - ), - ] diff --git a/app/grants/migrations/0005_auto_20180918_2328.py b/app/grants/migrations/0005_auto_20180918_2328.py deleted file mode 100644 index df0f6a6c613..00000000000 --- a/app/grants/migrations/0005_auto_20180918_2328.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.1 on 2018-09-18 23:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('grants', '0004_auto_20180918_2322'), - ] - - operations = [ - migrations.AlterField( - model_name='grant', - name='frequency', - field=models.DecimalField(decimal_places=0, default=30, max_digits=50), - ), - ] diff --git a/app/grants/migrations/0006_grant_tokenaddress.py b/app/grants/migrations/0006_grant_tokenaddress.py deleted file mode 100644 index 40edcc8c575..00000000000 --- a/app/grants/migrations/0006_grant_tokenaddress.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.1 on 2018-09-19 15:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('grants', '0005_auto_20180918_2328'), - ] - - operations = [ - migrations.AddField( - model_name='grant', - name='tokenAddress', - field=models.CharField(default='0x0', max_length=255), - ), - ] diff --git a/app/grants/migrations/0007_auto_20180920_2115.py b/app/grants/migrations/0007_auto_20180920_2115.py deleted file mode 100644 index 8cae68a9fb4..00000000000 --- a/app/grants/migrations/0007_auto_20180920_2115.py +++ /dev/null @@ -1,57 +0,0 @@ -# Generated by Django 2.1.1 on 2018-09-20 21:15 - -from django.db import migrations, models -import django.db.models.deletion -import economy.models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dashboard', '0108_auto_20180917_2137'), - ('grants', '0006_grant_tokenaddress'), - ] - - operations = [ - migrations.CreateModel( - name='Contribution', - 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)), - ('txId', models.CharField(default='0x0', max_length=255)), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='Subscription', - 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)), - ('status', models.BooleanField(default=True)), - ('subscriptionHash', models.CharField(default='', max_length=255)), - ('contributorSignature', models.CharField(default='', max_length=255)), - ('contributorAddress', models.CharField(default='', max_length=255)), - ('amountPerPeriod', models.DecimalField(decimal_places=4, default=1, max_digits=50)), - ('tokenAddress', models.CharField(default='0x0', max_length=255)), - ('gasPrice', models.DecimalField(decimal_places=4, default=1, max_digits=50)), - ('network', models.CharField(default='0x0', max_length=255)), - ('contributorProfile', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='grant_contributor', to='dashboard.Profile')), - ('grantPk', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='grant_subscription', to='grants.Grant')), - ], - options={ - 'abstract': False, - }, - ), - migrations.DeleteModel( - name='Stakeholder', - ), - migrations.AddField( - model_name='contribution', - name='subscriptionPk', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subscription_contribution', to='grants.Subscription'), - ), - ] diff --git a/app/grants/migrations/0008_auto_20180927_1537.py b/app/grants/migrations/0008_auto_20180927_1537.py deleted file mode 100644 index 9b0fd5ac3d0..00000000000 --- a/app/grants/migrations/0008_auto_20180927_1537.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 2.1.1 on 2018-09-27 15:37 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('grants', '0007_auto_20180920_2115'), - ] - - operations = [ - migrations.RenameField( - model_name='grant', - old_name='adminAddress', - new_name='admin_address', - ), - migrations.RenameField( - model_name='grant', - old_name='amountGoal', - new_name='amount_goal', - ), - migrations.RenameField( - model_name='grant', - old_name='amountReceived', - new_name='amount_received', - ), - migrations.RenameField( - model_name='grant', - old_name='tokenAddress', - new_name='contract_address', - ), - migrations.AddField( - model_name='grant', - name='network', - field=models.CharField(default='0x0', max_length=255), - ), - migrations.AddField( - model_name='grant', - name='token_address', - field=models.CharField(default='0x0', max_length=255), - ), - ] diff --git a/app/grants/migrations/0009_auto_20180927_1544.py b/app/grants/migrations/0009_auto_20180927_1544.py deleted file mode 100644 index d8bc8cd5665..00000000000 --- a/app/grants/migrations/0009_auto_20180927_1544.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.1.1 on 2018-09-27 15:44 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('grants', '0008_auto_20180927_1537'), - ] - - operations = [ - migrations.RenameField( - model_name='grant', - old_name='adminProfile', - new_name='admin_profile', - ), - migrations.RenameField( - model_name='grant', - old_name='teamMemberProfiles', - new_name='team_member_profiles', - ), - ] diff --git a/app/grants/migrations/0010_auto_20180927_1643.py b/app/grants/migrations/0010_auto_20180927_1643.py deleted file mode 100644 index 86ca7eaafb8..00000000000 --- a/app/grants/migrations/0010_auto_20180927_1643.py +++ /dev/null @@ -1,63 +0,0 @@ -# Generated by Django 2.1.1 on 2018-09-27 16:43 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('grants', '0009_auto_20180927_1544'), - ] - - operations = [ - migrations.RenameField( - model_name='contribution', - old_name='subscriptionPk', - new_name='subscription_pk', - ), - migrations.RenameField( - model_name='contribution', - old_name='txId', - new_name='tx_id', - ), - migrations.RenameField( - model_name='subscription', - old_name='amountPerPeriod', - new_name='amount_per_period', - ), - migrations.RenameField( - model_name='subscription', - old_name='contributorAddress', - new_name='contributor_address', - ), - migrations.RenameField( - model_name='subscription', - old_name='contributorProfile', - new_name='contributor_profile', - ), - migrations.RenameField( - model_name='subscription', - old_name='contributorSignature', - new_name='contributor_signature', - ), - migrations.RenameField( - model_name='subscription', - old_name='gasPrice', - new_name='gas_price', - ), - migrations.RenameField( - model_name='subscription', - old_name='grantPk', - new_name='grant_pk', - ), - migrations.RenameField( - model_name='subscription', - old_name='subscriptionHash', - new_name='subscription_hash', - ), - migrations.RenameField( - model_name='subscription', - old_name='tokenAddress', - new_name='token_address', - ), - ] diff --git a/app/grants/migrations/0011_grant_required_gas_price.py b/app/grants/migrations/0011_grant_required_gas_price.py deleted file mode 100644 index be897f545d0..00000000000 --- a/app/grants/migrations/0011_grant_required_gas_price.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.1 on 2018-09-27 19:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('grants', '0010_auto_20180927_1643'), - ] - - operations = [ - migrations.AddField( - model_name='grant', - name='required_gas_price', - field=models.DecimalField(decimal_places=0, default='0', max_digits=50), - ), - ] diff --git a/app/grants/migrations/0012_auto_20181001_0010.py b/app/grants/migrations/0012_auto_20181001_0010.py deleted file mode 100644 index ec253f89a90..00000000000 --- a/app/grants/migrations/0012_auto_20181001_0010.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.1.1 on 2018-10-01 00:10 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('grants', '0011_grant_required_gas_price'), - ] - - operations = [ - migrations.RemoveField( - model_name='subscription', - name='grant_pk', - ), - migrations.AddField( - model_name='grant', - name='transaction_hash', - field=models.CharField(default='0x0', max_length=255), - ), - migrations.AddField( - model_name='subscription', - name='grant', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subscriptions', to='grants.Grant'), - ), - ] diff --git a/app/grants/models.py b/app/grants/models.py index 889c5b8e9cb..f11be160ed4 100644 --- a/app/grants/models.py +++ b/app/grants/models.py @@ -1,5 +1,24 @@ +# -*- coding: utf-8 -*- +"""Define the Grant models. + +Copyright (C) 2018 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 django.db import models -from django.contrib.humanize.templatetags.humanize import naturalday, naturaltime + from economy.models import SuperModel @@ -20,18 +39,23 @@ class Grant(SuperModel): transaction_hash = models.CharField(max_length=255, default='0x0') network = models.CharField(max_length=255, default='0x0') required_gas_price = models.DecimalField(default='0', decimal_places=0, max_digits=50) - admin_profile = models.ForeignKey('dashboard.Profile', related_name='grant_admin', on_delete=models.CASCADE, null=True) team_member_profiles = models.ManyToManyField('dashboard.Profile', related_name='grant_team_members') def percentage_done(self): - # import ipdb; ipdb.set_trace() return ((self.amount_received / self.amount_goal) * 100) - def __str__(self): """Return the string representation of a Grant.""" - return f" id: {self.pk}, status: {self.status}, title: {self.title}, description: {self.description}, reference_url: {self.reference_url}, image_url: {self.image_url}, admin_address: {self.admin_address}, frequency: {self.frequency}, amount_goal: {self.amount_goal}, amount_received: {self.amount_received}, token_address: {self.token_address}, contract_address: {self.contract_address}, transaction_hash: {self.transaction_hash}, network: {self.network}, admin_profile: {self.admin_profile}, team_member_profiles: {self.team_member_profiles} @ {naturaltime(self.created_on)}" + # , reference_url: {self.reference_url}, image_url: {self.image_url}, + # admin_address: {self.admin_address}, frequency: {self.frequency}, + # amount_goal: {self.amount_goal}, amount_received: {self.amount_received}, + # token_address: {self.token_address}, contract_address: {self.contract_address}, + # transaction_hash: {self.transaction_hash}, network: {self.network}, + # admin_profile: {self.admin_profile}, team_member_profiles: + # {self.team_member_profiles} @ {naturaltime(self.created_on)} + return f"id: {self.pk}, status: {self.status}, title: {self.title}, description: {self.description}" + class Subscription(SuperModel): """Define the structure of a subscription agreement""" @@ -44,18 +68,25 @@ class Subscription(SuperModel): token_address = models.CharField(max_length=255, default='0x0') gas_price = models.DecimalField(default=1, decimal_places=4, max_digits=50) network = models.CharField(max_length=255, default='0x0') - - grant = models.ForeignKey('Grant', related_name='subscriptions', on_delete=models.CASCADE, null=True) - contributor_profile = models.ForeignKey('dashboard.Profile', related_name='grant_contributor', on_delete=models.CASCADE, null=True) + contributor_profile = models.ForeignKey( + 'dashboard.Profile', related_name='grant_contributor', on_delete=models.CASCADE, null=True + ) def __str__(self): """Return the string representation of a Subscription.""" - return f" id: {self.pk}, status: {self.status}, subscription_hash: {self.subscription_hash}, contributor_signature: {self.contributor_signature}, contributor_address: {self.contributor_address}, contributor_profile: {self.contributor_profile}, amount_per_period: {self.amount_per_period}, token_address: {self.token_address}, gas_price: {self.gas_price}, network: {self.network}, @ {naturaltime(self.created_on)}, grant: {self.grant_pk}" + # f" contributor_signature: {self.contributor_signature}, contributor_address:" \ + # f" {self.contributor_address}, contributor_profile: {self.contributor_profile}," \ + # f" amount_per_period: {self.amount_per_period}, token_address: {self.token_address}," \ + # f" gas_price: {self.gas_price}, network: {self.network}, @ {naturaltime(self.created_on)}," \ + # f" grant: {self.grant_pk}" + return f"id: {self.pk}, status: {self.status}, subscription_hash: {self.subscription_hash}" + class Contribution(SuperModel): """Define the structure of a subscription agreement""" tx_id = models.CharField(max_length=255, default='0x0') - - subscription_pk = models.ForeignKey('Subscription', related_name='subscription_contribution', on_delete=models.CASCADE, null=True) + subscription = models.ForeignKey( + 'grants.Subscription', related_name='subscription_contribution', on_delete=models.CASCADE, null=True + ) diff --git a/app/grants/templates/grants/cancel.html b/app/grants/templates/grants/cancel.html index 69aa8c54072..815711b92a6 100644 --- a/app/grants/templates/grants/cancel.html +++ b/app/grants/templates/grants/cancel.html @@ -1,105 +1,117 @@ -{% load static %} -{% load humanize %} +{% comment %} + Copyright (C) 2018 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 static humanize %} - - {% include 'shared/head.html' %} - {% include 'shared/cards.html' %} - - -{% include 'shared/tag_manager_2.html' %} -
- {% include 'shared/nav.html' %} -
- - + + {% include 'shared/head.html' %} + {% include 'shared/cards.html' %} + + + {% include 'shared/tag_manager_2.html' %} +
+ {% include 'shared/nav.html' %} +
+ +

+ {% trans "Cancel contribution for" %} + {{ grant.title }} +

-

- Cancel contribution for - {{grant.title}} -

+
+
+
+
+
+
+
+
+ 10 ETH +

{% trans "Your contribution" %}

+
+
+
-
-
-
-
-
-
-
-
- 10 ETH -

Your contribution

+
+
+
+ 344.ETH +

{% trans "Current" %}

+
+
+ 500.ETH +

{% trans "Goal" %}

+
+
-
-
-
+
- 344.ETH -

Current

+ {% trans "Start date:" %}05/05/2018
- 500.ETH -

Goal

+ {% trans "Effective date of cancel:" %} 05/12/2018
-
-
-
-
- Start date: 05/05/2018 -
-
- Effective date of cancel: 05/12/2018 +
+
+ +
+
+
+ {% csrf_token %} + +
+
+
-
-
- -
-
-
- {% csrf_token %} - -
-
-
-
- -
-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Mauris sodales magna sit amet mattis feugiat. - Etiam dapibus semper dapibus. - Sed sit amet vestibulum ligula, sit amet imperdiet risus. +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Mauris sodales magna sit amet mattis feugiat. + Etiam dapibus semper dapibus. + Sed sit amet vestibulum ligula, sit amet imperdiet risus. - Etiam efficitur sem vel arcu porta finibus. - Nullam sit amet dapibus nisi. Praesent elementum orci felis, id aliquet purus commodo ut. - Donec ut consequat purus, ornare malesuada dolor. Donec ligula lacus, facilisis ut sollicitudin vel, aliquam quis tellus. + Etiam efficitur sem vel arcu porta finibus. + Nullam sit amet dapibus nisi. Praesent elementum orci felis, id aliquet purus commodo ut. + Donec ut consequat purus, ornare malesuada dolor. Donec ligula lacus, facilisis ut sollicitudin vel, aliquam quis tellus. - Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Mauris sodales magna sit amet mattis feugiat. - Etiam dapibus semper dapibus. - Sed sit amet vestibulum ligula, sit amet imperdiet risus. -

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Mauris sodales magna sit amet mattis feugiat. + Etiam dapibus semper dapibus. + Sed sit amet vestibulum ligula, sit amet imperdiet risus. +

+
+
-
-
- -{% include 'shared/bottom_notification.html' %} -{% include 'shared/analytics.html' %} -{% include 'shared/footer_scripts.html' %} -{% include 'shared/footer.html' %} - - - - - - - - + {% include 'shared/bottom_notification.html' %} + {% include 'shared/analytics.html' %} + {% include 'shared/footer_scripts.html' %} + {% include 'shared/footer.html' %} + + + + + + + + diff --git a/app/grants/templates/grants/fund.html b/app/grants/templates/grants/fund.html index 5c8120b3611..c9b64eb40de 100644 --- a/app/grants/templates/grants/fund.html +++ b/app/grants/templates/grants/fund.html @@ -1,150 +1,149 @@ -{% load static %} -{% load humanize %} +{% comment %} + Copyright (C) 2018 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 static humanize %} - - {% include 'shared/head.html' %} - {% include 'shared/cards.html' %} - - - -{% include 'shared/tag_manager_2.html' %} -
- {% include 'shared/nav.html' %} -
- - - -

- Help - {{grant.adminProfile}} - get funding for - {{grant.title}} -

- -
-
Fund Grant
- -
- {% csrf_token %} -
- - - -
- -
-
- + + {% include 'shared/head.html' %} + {% include 'shared/cards.html' %} + + + {% include 'shared/tag_manager_2.html' %} +
+ {% include 'shared/nav.html' %} +
+ + +

+ {% trans "Help" %} + {{ grant.adminProfile }} + {% trans "get funding for" %} + {{ grant.title }} +

+ +
+
{% trans "Fund Grant" %}
+ + {% csrf_token %} +
+
+ +
+
+ +
+
+
+ +
+
+
-
-
- +
+ +
+
+
- -
-
- -
- -
-
-
-
-
- -
-
- - -
-
- -
-
- - -
-
+
+
+ +
+
-
-
-
-
-
-
- - 10 ETH -

Your contribution

-
+
+
+ +
-
-
-
- {{grant.amountReceived}} ETH -

Current

+
+
+
+
+
+
+ 10 ETH +

{% trans "Your contribution" %}

+
+
-
- {{grant.amountGoal}} ETH -

Goal

+ +
+
+
+ {{ grant.amountReceived }} ETH +

{% trans "Current" %}

+
+
+ {{ grant.amountGoal }} ETH +

{% trans "Goal" %}

+
+
+ +

+ {{ grant.description }} + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Mauris sodales magna sit amet mattis feugiat. + Etiam dapibus semper dapibus. + Sed sit amet vestibulum ligula, sit amet imperdiet risus. + + Etiam efficitur sem vel arcu porta finibus. + Nullam sit amet dapibus nisi. Praesent elementum orci felis, id aliquet purus commodo ut. + Donec ut consequat purus, ornare malesuada dolor. Donec ligula lacus, facilisis ut sollicitudin vel, aliquam quis tellus. +

- -

- {{grant.description}} - Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Mauris sodales magna sit amet mattis feugiat. - Etiam dapibus semper dapibus. - Sed sit amet vestibulum ligula, sit amet imperdiet risus. - - Etiam efficitur sem vel arcu porta finibus. - Nullam sit amet dapibus nisi. Praesent elementum orci felis, id aliquet purus commodo ut. - Donec ut consequat purus, ornare malesuada dolor. Donec ligula lacus, facilisis ut sollicitudin vel, aliquam quis tellus. -

-
+
+ + + + +
+
-
- - - - -
- - -
- -{% include 'shared/bottom_notification.html' %} -{% include 'shared/analytics.html' %} -{% include 'shared/footer_scripts.html' %} -{% include 'shared/footer.html' %} - - - - - - - - - - - - - - - - - + {% include 'shared/bottom_notification.html' %} + {% include 'shared/analytics.html' %} + {% include 'shared/footer_scripts.html' %} + {% include 'shared/footer.html' %} + + + + + + + + + + + + + + diff --git a/app/grants/templates/grants/index.html b/app/grants/templates/grants/index.html index d2fb06c3fa1..a5ea4ff0593 100644 --- a/app/grants/templates/grants/index.html +++ b/app/grants/templates/grants/index.html @@ -1,3 +1,19 @@ +{% comment %} + Copyright (C) 2018 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 humanize static i18n %} @@ -5,7 +21,7 @@ {% include 'shared/head.html' %} {% include 'shared/cards.html' %} - + {% include 'shared/tag_manager_2.html' %}
{% include 'shared/nav.html' %} @@ -64,11 +80,11 @@

{% trans 'Dev Grants' %}

{% if grants %}
{% for grant in grants %} - {% if forloop.counter0|divisibleby:3 %}
{% endif %} + {% if forloop.counter0|divisibleby:3 %}
{% endif %}
{% include 'grants/grant_card.html' %}
- {% if forloop.counter|divisibleby:3 or forloop.last %}
{% endif %} + {% if forloop.counter|divisibleby:3 or forloop.last %}
{% endif %} {% endfor %}
{% endif %} @@ -87,5 +103,4 @@

{% trans 'Dev Grants' %}

- diff --git a/app/grants/templates/grants/new.html b/app/grants/templates/grants/new.html index 21ba9ca918c..b96942e6228 100644 --- a/app/grants/templates/grants/new.html +++ b/app/grants/templates/grants/new.html @@ -1,3 +1,19 @@ +{% comment %} + Copyright (C) 2018 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 static %} @@ -5,7 +21,7 @@ {% include 'shared/head.html' %} {% include 'shared/cards.html' %} - + {% include 'shared/tag_manager_2.html' %}
{% include 'shared/nav.html' %} @@ -15,34 +31,34 @@
-

Create a Grant

-

We're excited to learn about your project. Complete the form below to get funding for your project.

+

{% trans "Create a Grant" %}

+

{% trans "We're excited to learn about your project. Complete the form below to get funding for your project." %}

-
+ {% csrf_token %}
- +
- +
- +
- +
@@ -56,25 +72,25 @@

Create a Grant

- - + +
- +
- +
- +
- Drag & Drop or Browse + {% trans "Drag & Drop or Browse" %}
@@ -120,11 +136,10 @@

Create a Grant

- + - diff --git a/app/grants/templates/grants/show.html b/app/grants/templates/grants/show.html index 4e67509e233..898cd85eed9 100644 --- a/app/grants/templates/grants/show.html +++ b/app/grants/templates/grants/show.html @@ -1,12 +1,27 @@ -{% load static %} -{% load humanize %} +{% comment %} + Copyright (C) 2018 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 static humanize %} {% include 'shared/head.html' %} {% include 'shared/cards.html' %} - + {% include 'shared/tag_manager_2.html' %}
{% include 'shared/nav.html' %} @@ -18,7 +33,7 @@
@@ -71,7 +88,7 @@

{{ grant.title }}

-

What we need.

+

{% trans "What we need." %}

{{ grant.description }}
@@ -92,5 +109,4 @@

What we need.

- diff --git a/app/grants/tests.py b/app/grants/tests.py deleted file mode 100644 index 7ce503c2dd9..00000000000 --- a/app/grants/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/app/grants/urls.py b/app/grants/urls.py index cd707d9adf0..7da07c53b64 100644 --- a/app/grants/urls.py +++ b/app/grants/urls.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Handle avatar URLs. +"""Handle grant URLs. Copyright (C) 2018 Gitcoin Core @@ -17,12 +17,9 @@ along with this program. If not, see . """ - from django.urls import path, re_path -from django.conf.urls import url - -from .views import cancel_subscription, fund_grant, grant_show, grants, new_grant +from grants.views import cancel_subscription, fund_grant, grant_show, grants, new_grant app_name = 'grants' urlpatterns = [ diff --git a/app/grants/utils.py b/app/grants/utils.py deleted file mode 100644 index e3a86c19f18..00000000000 --- a/app/grants/utils.py +++ /dev/null @@ -1,548 +0,0 @@ -# -*- coding: utf-8 -*- -"""Define Dashboard related utilities and miscellaneous logic. - -Copyright (C) 2018 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 . - -""" -import json -import logging -from json.decoder import JSONDecodeError - -from django.conf import settings - -import ipfsapi -import requests -from dashboard.helpers import UnsupportedSchemaException, normalize_url, process_bounty_changes, process_bounty_details -from dashboard.models import Activity, Bounty, UserAction, Grant, Subscription, Contribution -from eth_utils import to_checksum_address -from gas.utils import conf_time_spread, eth_usd_conv_rate, gas_advisories, recommend_min_gas_price_to_confirm_in_time -from hexbytes import HexBytes -from ipfsapi.exceptions import CommunicationError -from web3 import HTTPProvider, Web3 -from web3.exceptions import BadFunctionCallOutput - -logger = logging.getLogger(__name__) - - -class GrantNotFoundException(Exception): - pass - - -class UnsupportedNetworkException(Exception): - pass - - -class IPFSCantConnectException(Exception): - pass - - -class NoBountiesException(Exception): - pass - - -def humanize_event_name(name): - """Humanize an event name. - - Args: - name (str): The event name - - Returns: - str: The humanized representation. - - """ - humanized_event_names = { - 'new_bounty': 'New funded issue', - 'start_work': 'Work started', - 'stop_work': 'Work stopped', - 'work_submitted': 'Work submitted', - 'increased_bounty': 'Increased funds for issue', - 'killed_bounty': 'Cancelled funded issue', - 'worker_approved': 'Worker approved', - 'worker_rejected': 'Worker rejected', - 'work_done': 'Work done' - } - - return humanized_event_names.get(name, name).upper() - - -def create_user_action(user, action_type, request=None, metadata=None): - """Create a UserAction for the specified action type. - - Args: - user (User): The User object. - action_type (str): The type of action to record. - request (Request): The request object. Defaults to: None. - metadata (dict): Any accompanying metadata to be added. - Defaults to: {}. - - Returns: - bool: Whether or not the UserAction was created successfully. - - """ - from app.utils import handle_location_request - if action_type not in dict(UserAction.ACTION_TYPES).keys(): - print('UserAction.create_action received an invalid action_type') - return False - - if metadata is None: - metadata = {} - - kwargs = { - 'metadata': metadata, - 'action': action_type, - 'user': user - } - - if request: - geolocation_data, ip_address = handle_location_request(request) - - if geolocation_data: - kwargs['location_data'] = geolocation_data - if ip_address: - kwargs['ip_address'] = ip_address - - if user and hasattr(user, 'profile'): - kwargs['profile'] = user.profile if user and user.profile else None - - try: - UserAction.objects.create(**kwargs) - return True - except Exception as e: - logger.error(f'Failure in UserAction.create_action - ({e})') - return False - - -def get_ipfs(host=None, port=settings.IPFS_API_PORT): - """Establish a connection to IPFS. - - Args: - host (str): The IPFS host to connect to. - Defaults to environment variable: IPFS_HOST. - port (int): The IPFS port to connect to. - Defaults to environment variable: env IPFS_API_PORT. - - Raises: - CommunicationError: The exception is raised when there is a - communication error with IPFS. - - Returns: - ipfsapi.client.Client: The IPFS connection client. - - """ - if host is None: - host = f'https://{settings.IPFS_HOST}' - - try: - return ipfsapi.connect(host, port) - except CommunicationError as e: - logger.exception(e) - raise IPFSCantConnectException('Failed while attempt to connect to IPFS') - return None - - -def ipfs_cat(key): - try: - # Attempt connecting to IPFS via Infura - response, status_code = ipfs_cat_requests(key) - if status_code == 200: - return response - - # Attempt connecting to IPFS via hosted node - response = ipfs_cat_ipfsapi(key) - if response: - return response - - raise IPFSCantConnectException('Failed to connect cat key against IPFS - Check IPFS/Infura connectivity') - except IPFSCantConnectException as e: - logger.exception(e) - - -def ipfs_cat_ipfsapi(key): - ipfs = get_ipfs() - if ipfs: - try: - return ipfs.cat(key) - except Exception: - return None - - -def ipfs_cat_requests(key): - try: - url = f'https://ipfs.infura.io:5001/api/v0/cat/{key}' - response = requests.get(url, timeout=1) - return response.text, response.status_code - except: - return None, 500 - - -def get_web3(network): - """Get a Web3 session for the provided network. - - Attributes: - network (str): The network to establish a session with. - - Raises: - UnsupportedNetworkException: The exception is raised if the method - is passed an invalid network. - - Returns: - web3.main.Web3: A web3 instance for the provided network. - - """ - if network in ['mainnet', 'rinkeby', 'ropsten']: - return Web3(HTTPProvider(f'https://{network}.infura.io')) - raise UnsupportedNetworkException(network) - -# we will need to work this out since there are N Subscription contracts -def getSubscriptionContractAddresss(network): - if network == 'mainnet': - return to_checksum_address('0x2af47a65da8cd66729b4209c22017d6a5c2d2400') - elif network == 'rinkeby': - return to_checksum_address('0xf209d2b723b6417cbf04c07e733bee776105a073') - raise UnsupportedNetworkException(network) - - -# http://web3py.readthedocs.io/en/latest/contracts.html -def getBountyContract(network): - web3 = get_web3(network) - standardbounties_abi = '[{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"}],"name":"killBounty","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_bountyId","type":"uint256"}],"name":"getBountyToken","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_data","type":"string"}],"name":"fulfillBounty","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_newDeadline","type":"uint256"}],"name":"extendDeadline","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getNumBounties","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_fulfillmentId","type":"uint256"},{"name":"_data","type":"string"}],"name":"updateFulfillment","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_newFulfillmentAmount","type":"uint256"},{"name":"_value","type":"uint256"}],"name":"increasePayout","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_newFulfillmentAmount","type":"uint256"}],"name":"changeBountyFulfillmentAmount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_newIssuer","type":"address"}],"name":"transferIssuer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_value","type":"uint256"}],"name":"activateBounty","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_issuer","type":"address"},{"name":"_deadline","type":"uint256"},{"name":"_data","type":"string"},{"name":"_fulfillmentAmount","type":"uint256"},{"name":"_arbiter","type":"address"},{"name":"_paysTokens","type":"bool"},{"name":"_tokenContract","type":"address"}],"name":"issueBounty","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_issuer","type":"address"},{"name":"_deadline","type":"uint256"},{"name":"_data","type":"string"},{"name":"_fulfillmentAmount","type":"uint256"},{"name":"_arbiter","type":"address"},{"name":"_paysTokens","type":"bool"},{"name":"_tokenContract","type":"address"},{"name":"_value","type":"uint256"}],"name":"issueAndActivateBounty","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"_bountyId","type":"uint256"}],"name":"getBountyArbiter","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_value","type":"uint256"}],"name":"contribute","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_newPaysTokens","type":"bool"},{"name":"_newTokenContract","type":"address"}],"name":"changeBountyPaysTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_bountyId","type":"uint256"}],"name":"getBountyData","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_fulfillmentId","type":"uint256"}],"name":"getFulfillment","outputs":[{"name":"","type":"bool"},{"name":"","type":"address"},{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_newArbiter","type":"address"}],"name":"changeBountyArbiter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_newDeadline","type":"uint256"}],"name":"changeBountyDeadline","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_fulfillmentId","type":"uint256"}],"name":"acceptFulfillment","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"bounties","outputs":[{"name":"issuer","type":"address"},{"name":"deadline","type":"uint256"},{"name":"data","type":"string"},{"name":"fulfillmentAmount","type":"uint256"},{"name":"arbiter","type":"address"},{"name":"paysTokens","type":"bool"},{"name":"bountyStage","type":"uint8"},{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_bountyId","type":"uint256"}],"name":"getBounty","outputs":[{"name":"","type":"address"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"bool"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_bountyId","type":"uint256"},{"name":"_newData","type":"string"}],"name":"changeBountyData","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_bountyId","type":"uint256"}],"name":"getNumFulfillments","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_owner","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"bountyId","type":"uint256"}],"name":"BountyIssued","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"bountyId","type":"uint256"},{"indexed":false,"name":"issuer","type":"address"}],"name":"BountyActivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"bountyId","type":"uint256"},{"indexed":true,"name":"fulfiller","type":"address"},{"indexed":true,"name":"_fulfillmentId","type":"uint256"}],"name":"BountyFulfilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_bountyId","type":"uint256"},{"indexed":false,"name":"_fulfillmentId","type":"uint256"}],"name":"FulfillmentUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"bountyId","type":"uint256"},{"indexed":true,"name":"fulfiller","type":"address"},{"indexed":true,"name":"_fulfillmentId","type":"uint256"}],"name":"FulfillmentAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"bountyId","type":"uint256"},{"indexed":true,"name":"issuer","type":"address"}],"name":"BountyKilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"bountyId","type":"uint256"},{"indexed":true,"name":"contributor","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"ContributionAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"bountyId","type":"uint256"},{"indexed":false,"name":"newDeadline","type":"uint256"}],"name":"DeadlineExtended","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"bountyId","type":"uint256"}],"name":"BountyChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_bountyId","type":"uint256"},{"indexed":true,"name":"_newIssuer","type":"address"}],"name":"IssuerTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_bountyId","type":"uint256"},{"indexed":false,"name":"_newFulfillmentAmount","type":"uint256"}],"name":"PayoutIncreased","type":"event"}]' - standardbounties_addr = getStandardBountiesContractAddresss(network) - bounty_abi = json.loads(standardbounties_abi) - getBountyContract = web3.eth.contract(standardbounties_addr, abi=bounty_abi) - return getBountyContract - - -def get_bounty(bounty_enum, network): - if (settings.DEBUG or settings.ENV != 'prod') and network == 'mainnet': - # This block will return {} if env isn't prod and the network is mainnet. - print("--*--") - return {} - - standard_bounties = getBountyContract(network) - - try: - issuer, contract_deadline, fulfillmentAmount, paysTokens, bountyStage, balance = standard_bounties.functions.getBounty(bounty_enum).call() - except BadFunctionCallOutput: - raise BountyNotFoundException - # pull from blockchain - bountydata = standard_bounties.functions.getBountyData(bounty_enum).call() - arbiter = standard_bounties.functions.getBountyArbiter(bounty_enum).call() - token = standard_bounties.functions.getBountyToken(bounty_enum).call() - bounty_data_str = ipfs_cat(bountydata) - bounty_data = json.loads(bounty_data_str) - - # fulfillments - num_fulfillments = int(standard_bounties.functions.getNumFulfillments(bounty_enum).call()) - fulfillments = [] - for fulfill_enum in range(0, num_fulfillments): - - # pull from blockchain - accepted, fulfiller, data = standard_bounties.functions.getFulfillment(bounty_enum, fulfill_enum).call() - try: - data_str = ipfs_cat(data) - data = json.loads(data_str) - except JSONDecodeError: - logger.error(f'Could not get {data} from ipfs') - continue - - # validation - if 'Failed to get block' in str(data_str): - raise IPFSCantConnectException("Failed to connect to IPFS") - - fulfillments.append({ - 'id': fulfill_enum, - 'accepted': accepted, - 'fulfiller': fulfiller, - 'data': data, - }) - - # validation - if 'Failed to get block' in str(bounty_data_str): - raise IPFSCantConnectException("Failed to connect to IPFS") - - # https://github.com/Bounties-Network/StandardBounties/issues/25 - ipfs_deadline = bounty_data.get('payload', {}).get('expire_date', False) - deadline = contract_deadline - if ipfs_deadline: - deadline = ipfs_deadline - - # assemble the data - bounty = { - 'id': bounty_enum, - 'issuer': issuer, - 'deadline': deadline, - 'contract_deadline': contract_deadline, - 'ipfs_deadline': ipfs_deadline, - 'fulfillmentAmount': fulfillmentAmount, - 'paysTokens': paysTokens, - 'bountyStage': bountyStage, - 'balance': balance, - 'data': bounty_data, - 'arbiter': arbiter, - 'token': token, - 'fulfillments': fulfillments, - 'network': network, - } - return bounty - - -# processes a bounty returned by get_bounty -def web3_process_bounty(bounty_data): - """Process web3 bounty data by creating new or updated Bounty objects.""" - # Check whether or not the bounty data payload is for mainnet and env is prod or other network and not mainnet. - if not bounty_data or (settings.DEBUG or settings.ENV != 'prod') and bounty_data.get('network') == 'mainnet': - # This block will return None if running in debug/non-prod env and the network is mainnet. - print(f"--*--") - return None - - did_change, old_bounty, new_bounty = process_bounty_details(bounty_data) - - if did_change and new_bounty: - _from = old_bounty.pk if old_bounty else None - print(f"- processing changes, {_from} => {new_bounty.pk}") - process_bounty_changes(old_bounty, new_bounty) - - return did_change, old_bounty, new_bounty - - -def has_tx_mined(txid, network): - web3 = get_web3(network) - try: - transaction = web3.eth.getTransaction(txid) - return transaction.blockHash != HexBytes('0x0000000000000000000000000000000000000000000000000000000000000000') - except Exception: - return False - - -def get_bounty_id(issue_url, network): - issue_url = normalize_url(issue_url) - bounty_id = get_bounty_id_from_db(issue_url, network) - if bounty_id: - return bounty_id - - all_known_stdbounties = Bounty.objects.filter( - web3_type='bounties_network', - network=network, - ).nocache().order_by('-standard_bounties_id') - - try: - highest_known_bounty_id = get_highest_known_bounty_id(network) - bounty_id = get_bounty_id_from_web3(issue_url, network, highest_known_bounty_id, direction='down') - except NoBountiesException: - last_known_bounty_id = 0 - if all_known_stdbounties.exists(): - last_known_bounty_id = all_known_stdbounties.first().standard_bounties_id - bounty_id = get_bounty_id_from_web3(issue_url, network, last_known_bounty_id, direction='up') - - return bounty_id - - -def get_bounty_id_from_db(issue_url, network): - issue_url = normalize_url(issue_url) - bounties = Bounty.objects.filter( - github_url=issue_url, - network=network, - web3_type='bounties_network', - ).nocache().order_by('-standard_bounties_id') - if not bounties.exists(): - return None - return bounties.first().standard_bounties_id - - -def get_highest_known_bounty_id(network): - standard_bounties = getBountyContract(network) - num_bounties = int(standard_bounties.functions.getNumBounties().call()) - if num_bounties == 0: - raise NoBountiesException() - return num_bounties - 1 - - -def get_bounty_id_from_web3(issue_url, network, start_bounty_id, direction='up'): - issue_url = normalize_url(issue_url) - - # iterate through all the bounties - bounty_enum = start_bounty_id - more_bounties = True - while more_bounties: - try: - - # pull and process each bounty - print(f'** get_bounty_id_from_web3; looking at {bounty_enum}') - bounty = get_bounty(bounty_enum, network) - url = bounty.get('data', {}).get('payload', {}).get('webReferenceURL', False) - if url == issue_url: - return bounty['id'] - - except BountyNotFoundException: - more_bounties = False - except UnsupportedSchemaException: - pass - finally: - # prepare for next loop - if direction == 'up': - bounty_enum += 1 - else: - bounty_enum -= 1 - - return None - - -def build_profile_pairs(bounty): - """Build the profile pairs list of tuples for ingestion by notifications. - - Args: - bounty (dashboard.models.Bounty): The Bounty to build profile pairs for. - - Returns: - list of tuples: The list of profile pair tuples. - - """ - profile_handles = [] - for fulfillment in bounty.fulfillments.select_related('profile').all().order_by('pk'): - if fulfillment.profile and fulfillment.profile.handle.strip() and fulfillment.profile.absolute_url: - profile_handles.append((fulfillment.profile.handle, fulfillment.profile.absolute_url)) - else: - addr = f"https://etherscan.io/address/{fulfillment.fulfiller_address}" - profile_handles.append((fulfillment.fulfiller_address, addr)) - return profile_handles - - -def get_ordinal_repr(num): - """Handle cardinal to ordinal representation of numeric values. - - Args: - num (int): The integer to be converted from cardinal to ordinal numerals. - - Returns: - str: The ordinal representation of the provided integer. - - """ - ordinal_suffixes = {1: 'st', 2: 'nd', 3: 'rd'} - if 10 <= num % 100 <= 20: - suffix = 'th' - else: - suffix = ordinal_suffixes.get(num % 10, 'th') - return f'{num}{suffix}' - - - -def record_user_action_on_interest(interest, event_name, last_heard_from_user_days): - """Record User actions and activity for the associated Interest.""" - payload = { - 'profile': interest.profile, - 'metadata': { - 'bounties': list(interest.bounty_set.values_list('pk', flat=True)), - 'interest_pk': interest.pk, - 'last_heard_from_user_days': last_heard_from_user_days, - } - } - UserAction.objects.create(action=event_name, **payload) - - if event_name in ['bounty_abandonment_escalation_to_mods', 'bounty_abandonment_warning']: - payload['needs_review'] = True - - Activity.objects.create(activity_type=event_name, bounty=interest.bounty_set.last(), **payload) - - -def get_context(ref_object=None, github_username='', user=None, confirm_time_minutes_target=4, - confirm_time_slow=120, confirm_time_avg=15, confirm_time_fast=1, active='', - title='', update=None): - """Get the context dictionary for use in view.""" - context = { - 'githubUsername': github_username, # TODO: Deprecate this field. - 'action_urls': ref_object.action_urls() if hasattr(ref_object, 'action_urls') else None, - 'active': active, - 'recommend_gas_price': recommend_min_gas_price_to_confirm_in_time(confirm_time_minutes_target), - 'recommend_gas_price_slow': recommend_min_gas_price_to_confirm_in_time(confirm_time_slow), - 'recommend_gas_price_avg': recommend_min_gas_price_to_confirm_in_time(confirm_time_avg), - 'recommend_gas_price_fast': recommend_min_gas_price_to_confirm_in_time(confirm_time_fast), - 'eth_usd_conv_rate': eth_usd_conv_rate(), - 'conf_time_spread': conf_time_spread(), - 'email': getattr(user, 'email', ''), - 'handle': getattr(user, 'username', ''), - 'title': title, - 'gas_advisories': gas_advisories(), - } - if ref_object is not None: - context.update({f'{ref_object.__class__.__name__}'.lower(): ref_object}) - if update is not None and isinstance(update, dict): - context.update(update) - return context - - -def clean_bounty_url(url): - """Clean the Bounty URL of unsavory characters. - - The primary utility of this method is to drop #issuecomment blocks from - Github issue URLs copy/pasted via comments. - - Args: - url (str): The Bounty VC URL. - - TODO: - * Deprecate this in favor of Django forms. - - Returns: - str: The cleaned Bounty URL. - - """ - try: - return url.split('#')[0] - except Exception: - return url - - -def generate_pub_priv_keypair(): - # Thanks https://github.com/vkobel/ethereum-generate-wallet/blob/master/LICENSE.md - from ecdsa import SigningKey, SECP256k1 - import sha3 - - def checksum_encode(addr_str): - keccak = sha3.keccak_256() - out = '' - addr = addr_str.lower().replace('0x', '') - keccak.update(addr.encode('ascii')) - hash_addr = keccak.hexdigest() - for i, c in enumerate(addr): - if int(hash_addr[i], 16) >= 8: - out += c.upper() - else: - out += c - return '0x' + out - - keccak = sha3.keccak_256() - - priv = SigningKey.generate(curve=SECP256k1) - pub = priv.get_verifying_key().to_string() - - keccak.update(pub) - address = keccak.hexdigest()[24:] - - def test(addrstr): - assert(addrstr == checksum_encode(addrstr)) - - test('0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed') - test('0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359') - test('0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB') - test('0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb') - test('0x7aA3a964CC5B0a76550F549FC30923e5c14EDA84') - - # print("Private key:", priv.to_string().hex()) - # print("Public key: ", pub.hex()) - # print("Address: ", checksum_encode(address)) - # return priv key, pub key, address - - return priv.to_string().hex(), pub.hex(), checksum_encode(address) diff --git a/app/grants/views.py b/app/grants/views.py index e185ca45e99..42211338a87 100644 --- a/app/grants/views.py +++ b/app/grants/views.py @@ -1,23 +1,37 @@ -import json -from django.core.serializers.json import DjangoJSONEncoder +# -*- coding: utf-8 -*- +"""Define the Grant views. -import logging +Copyright (C) 2018 Gitcoin Core -logger = logging.getLogger(__name__) +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. -from django.template.response import TemplateResponse +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 . + +""" +import json +import logging -from marketing.models import Keyword from django.conf import settings -from django.http import JsonResponse from django.shortcuts import redirect -from django.views.decorators.csrf import csrf_exempt +from django.template.response import TemplateResponse + +from grants.models import Grant, Subscription +from marketing.models import Keyword from web3 import HTTPProvider, Web3 -from .models import Grant, Subscription -# web3.py instance +logger = logging.getLogger(__name__) w3 = Web3(HTTPProvider(settings.WEB3_HTTP_PROVIDER)) + def grants(request): """Handle grants explorer.""" grants = Grant.objects.all() @@ -32,6 +46,7 @@ def grants(request): def grant_show(request, grant_id): + """Display a grant.""" grant = Grant.objects.get(pk=grant_id) params = { @@ -42,18 +57,13 @@ def grant_show(request, grant_id): } return TemplateResponse(request, 'grants/show.html', params) + def new_grant(request): """Handle new grant.""" - profile_id = request.session.get('profile_id') profile = request.user.profile if request.user.is_authenticated else None - print('request', request.POST) - if request.method == "POST": grant = Grant() - - print('request', request.POST) - grant.title = request.POST.get('input-name') grant.description = request.POST.get('description') grant.reference_url = request.POST.get('reference_url') @@ -65,17 +75,11 @@ def new_grant(request): grant.transaction_hash = request.POST.get('transaction_hash') grant.contract_address = request.POST.get('contract_address') grant.network = request.POST.get('network') - grant.admin_profile = profile - # grant.teamMemberProfiles = Need to do a profile search based on enetered emails - grant.save() - return redirect(f'/grants/{grant.pk}') - else: - grant = {} - + grant = {} params = { 'active': 'grants', 'title': 'New Grant', @@ -86,14 +90,9 @@ def new_grant(request): return TemplateResponse(request, 'grants/new.html', params) - - def fund_grant(request, grant_id): """Handle grant funding.""" - # import ipdb; ipdb.set_trace() grant = Grant.objects.get(pk=grant_id) - - profile_id = request.session.get('profile_id') profile = request.user.profile if request.user.is_authenticated else None print("this is the username:", profile) @@ -101,12 +100,8 @@ def fund_grant(request, grant_id): print("this is the web3 instance", w3.eth.account) # make sure a user can only create one subscription per grant - if request.method == "POST": subscription = Subscription() - - print("it fired") - # subscriptionHash and ContributorSignature will be given from smartcontracts and web3 # subscription.subscriptionHash = request.POST.get('input-name') # subscription.contributorSignature = request.POST.get('description') @@ -119,13 +114,10 @@ def fund_grant(request, grant_id): # subscription.network = request.POST.get('amount_goal') subscription.contributor_profile = profile subscription.grant = grant - - subscription.save() else: subscription = {} - params = { 'active': 'dashboard', 'title': 'Fund Grant', @@ -133,15 +125,11 @@ def fund_grant(request, grant_id): 'grant': grant, 'keywords': json.dumps([str(key) for key in Keyword.objects.all().values_list('keyword', flat=True)]), } - - return TemplateResponse(request, 'grants/fund.html', params) -def cancel_subscription(request, subscription_id): - """Handle Cancelation of grant funding""" - profile_id = request.session.get('profile_id') - profile = request.user.profile if request.user.is_authenticated else None +def cancel_subscription(request, subscription_id): + """Handle Cancelation of grant funding.""" subscription = Subscription.objects.get(pk=subscription_id) grant = subscription.grant @@ -149,13 +137,8 @@ def cancel_subscription(request, subscription_id): print("this is the grant:", grant) if request.method == "POST": - subscription.status = False - subscription.save() - # else: - # subscription = {} - params = { 'title': 'Fund Grant',