From 6536121f9b374d6c7a8e7de0cc9c3e67944a0938 Mon Sep 17 00:00:00 2001 From: Chibuotu Amadi Date: Thu, 24 Sep 2020 03:58:33 +0100 Subject: [PATCH 1/3] feat(profile): delete portfolio item --- app/dashboard/models.py | 496 ++++++++++-------- .../templates/profiles/tab_portfolio.html | 5 + app/dashboard/views.py | 9 + 3 files changed, 286 insertions(+), 224 deletions(-) diff --git a/app/dashboard/models.py b/app/dashboard/models.py index 1020e1277d8..3e6b7a7a5d7 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -120,8 +120,8 @@ def keyword(self, keyword): """ return self.filter( - Q(metadata__issueKeywords__icontains=keyword) | \ - Q(title__icontains=keyword) | \ + Q(metadata__issueKeywords__icontains=keyword) | + Q(title__icontains=keyword) | Q(issue_description__icontains=keyword) ) @@ -139,7 +139,7 @@ def needs_review(self): .filter( activities__activity_type__in=['bounty_abandonment_escalation_to_mods', 'bounty_abandonment_warning'], activities__needs_review=True, - ) + ) def reviewed(self): """Filter results by bounties that have been reviewed.""" @@ -147,7 +147,7 @@ def reviewed(self): .filter( activities__activity_type__in=['bounty_abandonment_escalation_to_mods', 'bounty_abandonment_warning'], activities__needs_review=False, - ) + ) def has_applicant(self): """Filter results by bounties that have applicants.""" @@ -155,7 +155,7 @@ def has_applicant(self): .filter( activities__activity_type='worker_applied', activities__needs_review=False, - ) + ) def warned(self): """Filter results by bounties that have been warned for inactivity.""" @@ -163,7 +163,7 @@ def warned(self): .filter( activities__activity_type='bounty_abandonment_warning', activities__needs_review=True, - ) + ) def escalated(self): """Filter results by bounties that have been escalated for review.""" @@ -171,7 +171,7 @@ def escalated(self): .filter( activities__activity_type='bounty_abandonment_escalation_to_mods', activities__needs_review=True, - ) + ) def closed(self): """Filter results by bounties that have been closed on Github.""" @@ -188,16 +188,19 @@ def has_funds(self): """Fields that bonties table should index together.""" + + def get_bounty_index_together(): import copy index_together = [ - ["network", "idx_status"], - ["current_bounty", "network"], - ["current_bounty", "network", "idx_status"], - ["current_bounty", "network", "web3_created"], - ["current_bounty", "network", "idx_status", "web3_created"], - ] - additions = ['admin_override_and_hide', 'experience_level', 'is_featured', 'project_length', 'bounty_owner_github_username', 'event'] + ["network", "idx_status"], + ["current_bounty", "network"], + ["current_bounty", "network", "idx_status"], + ["current_bounty", "network", "web3_created"], + ["current_bounty", "network", "idx_status", "web3_created"], + ] + additions = ['admin_override_and_hide', 'experience_level', 'is_featured', + 'project_length', 'bounty_owner_github_username', 'event'] for addition in additions: for ele in copy.copy(index_together): index_together.append([addition] + ele) @@ -361,7 +364,8 @@ class Bounty(SuperModel): remarketed_count = models.PositiveSmallIntegerField(default=0, blank=True, null=True) fee_amount = models.DecimalField(default=0, decimal_places=18, max_digits=50) fee_tx_id = models.CharField(default="0x0", max_length=255, blank=True) - coupon_code = models.ForeignKey('dashboard.Coupon', blank=True, null=True, related_name='coupon', on_delete=models.SET_NULL) + coupon_code = models.ForeignKey('dashboard.Coupon', blank=True, null=True, + related_name='coupon', on_delete=models.SET_NULL) token_value_time_peg = models.DateTimeField(blank=True, null=True) token_value_in_usdt = models.DecimalField(default=0, decimal_places=2, max_digits=50, blank=True, null=True) @@ -379,16 +383,17 @@ class Bounty(SuperModel): admin_mark_as_remarket_ready = models.BooleanField( default=False, help_text=_('Admin override to mark as remarketing ready') ) - admin_override_org_name = models.CharField(max_length=255, blank=True) # TODO: Remove POST ORGS + admin_override_org_name = models.CharField(max_length=255, blank=True) # TODO: Remove POST ORGS admin_override_org_logo = models.ImageField( upload_to=get_upload_filename, null=True, blank=True, help_text=_('Organization Logo - Override'), - ) # TODO: Remove POST ORGS + ) # TODO: Remove POST ORGS attached_job_description = models.URLField(blank=True, null=True, db_index=True) chat_channel_id = models.CharField(max_length=255, blank=True, null=True) - event = models.ForeignKey('dashboard.HackathonEvent', related_name='bounties', null=True, on_delete=models.SET_NULL, blank=True) + event = models.ForeignKey('dashboard.HackathonEvent', related_name='bounties', + null=True, on_delete=models.SET_NULL, blank=True) hypercharge_mode = models.BooleanField( default=False, help_text=_('This bounty will be part of the hypercharged bounties') ) @@ -450,7 +455,6 @@ def save(self, *args, **kwargs): } } - def handle_event(self, event): """Handle a new BountyEvent, and potentially change state""" next_state = self.EVENT_HANDLERS.get(self.project_type, {}).get(self.bounty_state, {}).get(event.event_type) @@ -540,7 +544,7 @@ def has_applicant(self): .filter( activities__activity_type='worker_applied', activities__needs_review=False, - ) + ) @property def warned(self): @@ -549,7 +553,7 @@ def warned(self): .filter( activities__activity_type='bounty_abandonment_warning', activities__needs_review=True, - ) + ) @property def escalated(self): @@ -558,7 +562,7 @@ def escalated(self): .filter( activities__activity_type='bounty_abandonment_escalation_to_mods', activities__needs_review=True, - ) + ) @property def url(self): @@ -655,7 +659,7 @@ def org_profile(self): return None @property - def org_display_name(self): # TODO: Remove POST ORGS + def org_display_name(self): # TODO: Remove POST ORGS if self.admin_override_org_name: return self.admin_override_org_name return org_name(self.github_url) @@ -807,7 +811,7 @@ def status(self): is_traditional_bounty_type = self.project_type == 'traditional' try: has_tips = self.tips.filter(is_for_bounty_fulfiller=False).send_happy_path().exists() - if has_tips and is_traditional_bounty_type and not self.is_open : + if has_tips and is_traditional_bounty_type and not self.is_open: return 'done' if not self.is_open: if self.accepted: @@ -1264,7 +1268,8 @@ def can_remarket(self): result = False if self.last_remarketed: - minimum_wait_after_remarketing = self.last_remarketed + timezone.timedelta(minutes=settings.MINUTES_BETWEEN_RE_MARKETING) + minimum_wait_after_remarketing = self.last_remarketed + \ + timezone.timedelta(minutes=settings.MINUTES_BETWEEN_RE_MARKETING) if timezone.now() < minimum_wait_after_remarketing: result = False @@ -1335,10 +1340,10 @@ def post_save_bounty(sender, instance, created, **kwargs): profile = Profile.objects.filter(handle=HYPERCHARGE_BOUNTIES_PROFILE_HANDLE).first() if profile: metadata = { - 'title': title, - 'description': truncatechars(instance.issue_description_text, 500), - 'url': instance.get_absolute_url(), - 'ask': '#announce' + 'title': title, + 'description': truncatechars(instance.issue_description_text, 500), + 'url': instance.get_absolute_url(), + 'ask': '#announce' } activity = Activity.objects.create(profile=profile, activity_type='hypercharge_bounty', metadata=metadata, bounty=instance) @@ -1372,9 +1377,9 @@ class BountyEvent(SuperModel): ) bounty = models.ForeignKey('dashboard.Bounty', on_delete=models.CASCADE, - related_name='events') + related_name='events') created_by = models.ForeignKey('dashboard.Profile', - on_delete=models.SET_NULL, related_name='events', blank=True, null=True) + on_delete=models.SET_NULL, related_name='events', blank=True, null=True) event_type = models.CharField(max_length=50, choices=EVENT_TYPES) metadata = JSONField(default=dict, blank=True) @@ -1390,6 +1395,7 @@ def submitted(self): """Exclude results that have not been submitted.""" return self.exclude(fulfiller_address='0x0000000000000000000000000000000000000000') + class BountyFulfillment(SuperModel): """The structure of a fulfillment on a Bounty.""" @@ -1419,7 +1425,8 @@ class BountyFulfillment(SuperModel): ('OTHERS', 'OTHERS') ] - bounty = models.ForeignKey(Bounty, related_name='fulfillments', on_delete=models.CASCADE, help_text="the bounty against which the fulfillment is made") + bounty = models.ForeignKey(Bounty, related_name='fulfillments', on_delete=models.CASCADE, + help_text="the bounty against which the fulfillment is made") # TODO: RENAME fulfillment_id = models.IntegerField(null=True, blank=True, help_text="bounty's fulfillment number") @@ -1427,34 +1434,44 @@ class BountyFulfillment(SuperModel): # TODO: RETIRE fulfiller_metadata = JSONField(default=dict, blank=True) - fulfiller_address = models.CharField(max_length=50, null=True, blank=True, help_text="address to which amount is credited") - funder_address = models.CharField(max_length=50, null=True, blank=True, help_text="address from which amount is deducted") + fulfiller_address = models.CharField(max_length=50, null=True, blank=True, + help_text="address to which amount is credited") + funder_address = models.CharField(max_length=50, null=True, blank=True, + help_text="address from which amount is deducted") # TODO: rename to fulfiller_profile - profile = models.ForeignKey('dashboard.Profile', related_name='fulfilled', on_delete=models.CASCADE, null=True, help_text="fulfillers's profile") - funder_profile = models.ForeignKey('dashboard.Profile', null=True, blank=True, on_delete=models.CASCADE, help_text="funder's profile") - + profile = models.ForeignKey('dashboard.Profile', related_name='fulfilled', + on_delete=models.CASCADE, null=True, help_text="fulfillers's profile") + funder_profile = models.ForeignKey('dashboard.Profile', null=True, blank=True, + on_delete=models.CASCADE, help_text="funder's profile") # TODO: rename to hours_worked fulfiller_hours_worked = models.DecimalField(null=True, blank=True, decimal_places=2, max_digits=50) # TODO: rename to submission_url fulfiller_github_url = models.CharField(max_length=255, blank=True, null=True) funder_last_notified_on = models.DateTimeField(null=True, blank=True) - project = models.ForeignKey('dashboard.HackathonProject', blank=True, null=True, on_delete=models.SET_NULL, related_name='submissions') + project = models.ForeignKey('dashboard.HackathonProject', blank=True, null=True, + on_delete=models.SET_NULL, related_name='submissions') accepted = models.BooleanField(default=False, help_text="has the fulfillment been accepted by the funder") - accepted_on = models.DateTimeField(null=True, blank=True, help_text="date when the fulfillment was accepted by the funder") + accepted_on = models.DateTimeField( + null=True, blank=True, help_text="date when the fulfillment was accepted by the funder") - payout_type = models.CharField(max_length=20, null=True, blank=True, choices=PAYOUT_TYPE, help_text="payment type used to make the payment") - tenant = models.CharField(max_length=10, null=True, blank=True, choices=TENANT, help_text="specific tenant type under the payout_type") + payout_type = models.CharField(max_length=20, null=True, blank=True, choices=PAYOUT_TYPE, + help_text="payment type used to make the payment") + tenant = models.CharField(max_length=10, null=True, blank=True, choices=TENANT, + help_text="specific tenant type under the payout_type") - funder_identifier = models.CharField(max_length=50, null=True, blank=True, help_text="unique funder identifier used by when payout_type is fiat") - fulfiller_identifier = models.CharField(max_length=50, null=True, blank=True, help_text="unique fulfiller identifier used by when payout_type is fiat") + funder_identifier = models.CharField(max_length=50, null=True, blank=True, + help_text="unique funder identifier used by when payout_type is fiat") + fulfiller_identifier = models.CharField(max_length=50, null=True, blank=True, + help_text="unique fulfiller identifier used by when payout_type is fiat") token_name = models.CharField(max_length=10, blank=True, help_text="token/currency in which the payout is done") payout_tx_id = models.CharField(default="0x0", max_length=255, blank=True, help_text="transaction id") payout_status = models.CharField(max_length=10, choices=PAYOUT_STATUS, blank=True, help_text="payment status") - payout_amount = models.DecimalField(null=True, blank=True, decimal_places=4, max_digits=50, help_text="amount being paid out by funder") + payout_amount = models.DecimalField(null=True, blank=True, decimal_places=4, + max_digits=50, help_text="amount being paid out by funder") def __str__(self): """Define the string representation of BountyFulfillment. @@ -1465,26 +1482,22 @@ def __str__(self): """ return f'BountyFulfillment ID: ({self.pk}) - Bounty ID: ({self.bounty.pk})' - @property def fulfiller_email(self): if self.profile: return self.profile.email return None - @property def fulfiller_github_username(self): if self.profile: return self.profile.handle return None - @property def should_hide(self): return self.fulfiller_github_username in settings.BLOCKED_USERS - @property def to_json(self): """Define the JSON representation of BountyFulfillment. @@ -1572,7 +1585,7 @@ class SendCryptoAsset(SuperModel): ('error', 'error'), ('unknown', 'unknown'), ('dropped', 'dropped'), - ('not_subed', 'not_subed'), # not submitted to chain yet + ('not_subed', 'not_subed'), # not submitted to chain yet ) web3_type = models.CharField(max_length=50, default='v3') @@ -1617,8 +1630,8 @@ def __str__(self): """Return the string representation for a tip.""" if self.web3_type == 'yge': return f"({self.network}) - {self.status}{' ORPHAN' if not self.emails else ''} " \ - f"{self.amount} {self.tokenName} to {self.username} from {self.from_name or 'NA'}, " \ - f"created: {naturalday(self.created_on)}, expires: {naturalday(self.expires_date)}" + f"{self.amount} {self.tokenName} to {self.username} from {self.from_name or 'NA'}, " \ + f"created: {naturalday(self.created_on)}, expires: {naturalday(self.expires_date)}" status = 'funded' if self.txid else 'not funded' status = status if not self.receive_txid else 'received' return f"({self.web3_type}) {status} {self.amount} {self.tokenName} to {self.username} from {self.from_name or 'NA'}" @@ -1747,7 +1760,7 @@ def update_tx_status(self): from economy.tx import getReplacedTX self.tx_status, self.tx_time = get_tx_status(self.txid, self.network, self.created_on) - #handle scenario in which a txn has been replaced + # handle scenario in which a txn has been replaced if self.tx_status in ['pending', 'dropped', 'unknown', '']: new_tx = getReplacedTX(self.txid) if new_tx: @@ -1817,17 +1830,16 @@ def trigger_townsquare(instance): from townsquare.models import Comment network = instance.network if instance.network != 'mainnet' else '' if 'activity:' in instance.comments_priv: - activity=instance.attached_object + activity = instance.attached_object comment = f"Just sent a tip of {instance.amount} {network} ETH to @{instance.username}" comment = Comment.objects.create(profile=instance.sender_profile, activity=activity, comment=comment) if 'comment:' in instance.comments_priv: - _comment=instance.attached_object + _comment = instance.attached_object _comment.save() comment = f"Just sent a tip of {instance.amount} {network} ETH to @{instance.username}" - comment = Comment.objects.create(profile=instance.sender_profile, activity=_comment.activity, comment=comment) - - + comment = Comment.objects.create(profile=instance.sender_profile, + activity=_comment.activity, comment=comment) @property def receive_url(self): @@ -1858,7 +1870,6 @@ class TipPayoutException(Exception): pass - class TipPayout(SuperModel): """Model representing redemption of a Kudos @@ -1939,20 +1950,22 @@ def postsave_tip(sender, instance, created, **kwargs): source_type=ContentType.objects.get(app_label='dashboard', model='tip'), source_id=instance.pk, defaults={ - "created_on":instance.created_on, - "org_profile":instance.org_profile, - "from_profile":instance.sender_profile, - "to_profile":instance.recipient_profile, - "value_usd":value_usd, - "url":'https://gitcoin.co/tips', - "network":instance.network, - "txid":instance.txid, - "token_name":instance.tokenName, - "token_value":value_true, + "created_on": instance.created_on, + "org_profile": instance.org_profile, + "from_profile": instance.sender_profile, + "to_profile": instance.recipient_profile, + "value_usd": value_usd, + "url": 'https://gitcoin.co/tips', + "network": instance.network, + "txid": instance.txid, + "token_name": instance.tokenName, + "token_value": value_true, } - ) + ) # method for updating + + @receiver(pre_save, sender=Bounty, dispatch_uid="psave_bounty") def psave_bounty(sender, instance, **kwargs): idx_experience_level = { @@ -1993,10 +2006,9 @@ def psave_bounty(sender, instance, **kwargs): if not instance.balance: instance.balance = 0 - if not instance.bounty_owner_profile: if instance.bounty_owner_github_username: - profiles = Profile.objects.filter(handle=instance.bounty_owner_github_username.lower().replace('@','')) + profiles = Profile.objects.filter(handle=instance.bounty_owner_github_username.lower().replace('@', '')) if profiles.exists(): instance.bounty_owner_profile = profiles.first() @@ -2012,14 +2024,14 @@ def psave_bounty(sender, instance, **kwargs): source_type=ct, source_id=instance.pk, defaults={ - "created_on":instance.web3_created, - "title":instance.title, - "description":instance.issue_description, - "url":instance.url, - "visible_to":None, + "created_on": instance.web3_created, + "title": instance.title, + "description": instance.issue_description, + "url": instance.url, + "visible_to": None, 'img_url': instance.get_avatar_url(True), } - ) + ) # delete any old bounties if instance.prev_bounty and instance.prev_bounty.pk: for sr in SearchResult.objects.filter(source_type=ct, source_id=instance.prev_bounty.pk): @@ -2033,18 +2045,18 @@ def psave_bounty_fulfilll(sender, instance, **kwargs): source_type=ContentType.objects.get(app_label='dashboard', model='bountyfulfillment'), source_id=instance.pk, defaults={ - "created_on":instance.created_on, - "org_profile":instance.bounty.org_profile, - "from_profile":instance.bounty.bounty_owner_profile, - "to_profile":instance.profile, - "value_usd":instance.bounty.value_in_usdt_then, - "url":instance.bounty.url, - "network":instance.bounty.network, + "created_on": instance.created_on, + "org_profile": instance.bounty.org_profile, + "from_profile": instance.bounty.bounty_owner_profile, + "to_profile": instance.profile, + "value_usd": instance.bounty.value_in_usdt_then, + "url": instance.bounty.url, + "network": instance.bounty.network, "txid": instance.payout_tx_id, - "token_name":instance.bounty.token_name, - "token_value":instance.bounty.value_in_token, + "token_name": instance.bounty.token_name, + "token_value": instance.bounty.value_in_token, } - ) + ) class InterestQuerySet(models.QuerySet): @@ -2116,6 +2128,7 @@ def mark_for_review(self): self.save() return self + def auto_user_approve(interest, bounty): interest.pending = False interest.acceptance_date = timezone.now() @@ -2190,7 +2203,7 @@ def related_to(self, profile): tips = Tip.objects.filter(sender_profile=profile).all() activity_pks = [] for tip in tips: - if tip.comments_priv: + if tip.comments_priv: obj = tip.attached_object() if 'activity:' in tip.comments_priv: activity_pks.append(obj.pk) @@ -2198,7 +2211,6 @@ def related_to(self, profile): activity_pks.append(obj.activity.pk) posts.union(self.filter(pk__in=activity_pks)) - # Posts the user commented on comments = Comment.objects.filter(profile=profile).all() activity_pks = [_.activity.pk for _ in comments] @@ -2363,7 +2375,6 @@ def video_participants_count(self): except KeyError: return 0 - @property def action_url(self): if self.bounty: @@ -2428,12 +2439,11 @@ def text(self): html_str = render_to_string('shared/activity.html', params) soup = BeautifulSoup(html_str) txt = soup.get_text() - txt = txt.replace("\n","") + txt = txt.replace("\n", "") for i in range(0, 100): - txt = txt.replace(" ",' ') + txt = txt.replace(" ", ' ') return txt - def has_voted(self, user): poll = self.metadata.get('poll_choices') if poll: @@ -2453,7 +2463,8 @@ def view_props_for(self, user): vp.metadata['liked'] = False if self.likes.exists(): vp.metadata['liked'] = self.likes.filter(profile=user.profile).exists() - vp.metadata['likes_title'] = "Liked by " + ",".join(self.likes.values_list('profile__handle', flat=True)) + '. ' + vp.metadata['likes_title'] = "Liked by " + \ + ",".join(self.likes.values_list('profile__handle', flat=True)) + '. ' vp.metadata['favorite'] = self.favorites(user) vp.metadata['poll_answered'] = self.has_voted(user) @@ -2519,6 +2530,7 @@ def to_dict(self, fields=None, exclude=None): kwargs['exclude'] = exclude return model_to_dict(self, **kwargs) + @receiver(pre_save, sender=Activity, dispatch_uid="psave_activity") def psave_activity(sender, instance, **kwargs): if instance.bounty and instance.bounty.event: @@ -2550,8 +2562,6 @@ def post_add_activity(sender, instance, created, **kwargs): dupe.delete() - - class LabsResearch(SuperModel): """Define the structure of Labs Research object.""" @@ -2640,7 +2650,6 @@ def get_queryset(self): return ProfileQuerySet(self.model, using=self._db).slim() - class Repo(SuperModel): name = models.CharField(max_length=255) @@ -2664,7 +2673,8 @@ def __str__(self): class BlockedURLFilter(SuperModel): - expression = models.CharField(max_length=255, help_text='the expression to search for in order to block that github url (or website)') + expression = models.CharField( + max_length=255, help_text='the expression to search for in order to block that github url (or website)') comment = models.TextField(blank=True) def __str__(self): @@ -2690,6 +2700,7 @@ class HackathonRegistration(SuperModel): on_delete=models.CASCADE, help_text='User profile' ) + def __str__(self): return f"Name: {self.name}; Hackathon: {self.hackathon}; Referer: {self.referer}; Registrant: {self.registrant}" @@ -2702,7 +2713,7 @@ def post_add_HackathonRegistration(sender, instance, created, **kwargs): hackathonevent=instance.hackathon, activity_type='hackathon_registration', - ) + ) def default_tribes_expiration(): @@ -2712,10 +2723,10 @@ def default_tribes_expiration(): class TribesSubscription(SuperModel): plans = ( - ('LITE', 'Lite'), - ('PRO', 'Pro'), - ('LAUNCH', 'Launch'), - ) + ('LITE', 'Lite'), + ('PRO', 'Pro'), + ('LAUNCH', 'Launch'), + ) expires_on = models.DateTimeField(null=True, blank=True, default=default_tribes_expiration) tribe = models.ForeignKey('dashboard.Profile', on_delete=models.CASCADE, related_name='subscription') @@ -2794,7 +2805,8 @@ class Profile(SuperModel): max_num_issues_start_work = models.IntegerField(default=5) etc_address = models.CharField(max_length=255, default='', blank=True) preferred_payout_address = models.CharField(max_length=255, default='', blank=True) - preferred_kudos_wallet = models.OneToOneField('kudos.Wallet', related_name='preferred_kudos_wallet', on_delete=models.SET_NULL, null=True, blank=True) + preferred_kudos_wallet = models.OneToOneField( + 'kudos.Wallet', related_name='preferred_kudos_wallet', on_delete=models.SET_NULL, null=True, blank=True) max_tip_amount_usdt_per_tx = models.DecimalField(default=2500, decimal_places=2, max_digits=50) max_tip_amount_usdt_per_week = models.DecimalField(default=20000, decimal_places=2, max_digits=50) last_visit = models.DateTimeField(null=True, blank=True) @@ -2829,20 +2841,25 @@ class Profile(SuperModel): dominant_persona = models.CharField(max_length=25, choices=PERSONAS, blank=True) selected_persona = models.CharField(max_length=25, choices=PERSONAS, blank=True) longest_streak = models.IntegerField(default=0) - activity_level = models.CharField(max_length=10, blank=True, help_text=_('the users activity level (high, low, new)')) + activity_level = models.CharField(max_length=10, blank=True, help_text=_( + 'the users activity level (high, low, new)')) num_repeated_relationships = models.IntegerField(default=0) avg_hourly_rate = models.DecimalField(default=0, decimal_places=2, max_digits=50) success_rate = models.IntegerField(default=0) - reliability = models.CharField(max_length=10, blank=True, help_text=_('the users reliability level (high, medium, unproven)')) + reliability = models.CharField(max_length=10, blank=True, help_text=_( + 'the users reliability level (high, medium, unproven)')) as_dict = JSONField(default=dict, blank=True) rank_funder = models.IntegerField(default=0) rank_org = models.IntegerField(default=0) rank_coder = models.IntegerField(default=0) - referrer = models.ForeignKey('dashboard.Profile', related_name='referred', on_delete=models.CASCADE, null=True, db_index=True, blank=True) + referrer = models.ForeignKey('dashboard.Profile', related_name='referred', + on_delete=models.CASCADE, null=True, db_index=True, blank=True) tribe_description = models.TextField(default='', blank=True, help_text=_('HTML rich description describing tribe.')) - automatic_backup = models.BooleanField(default=False, help_text=_('automatic backup profile to cloud storage such as 3Box if the flag is true')) + automatic_backup = models.BooleanField(default=False, help_text=_( + 'automatic backup profile to cloud storage such as 3Box if the flag is true')) as_representation = JSONField(default=dict, blank=True) - tribe_priority = models.TextField(default='', blank=True, help_text=_('HTML rich description for what tribe priorities.')) + tribe_priority = models.TextField(default='', blank=True, help_text=_( + 'HTML rich description for what tribe priorities.')) tribes_cover_image = models.ImageField( upload_to=get_upload_filename, @@ -2861,21 +2878,27 @@ class Profile(SuperModel): help_text='Is this profile a Tribe (only applies to orgs)?', ) - average_rating = models.DecimalField(default=0, decimal_places=2, max_digits=50, help_text='avg feedback from those who theyve done work with') + average_rating = models.DecimalField(default=0, decimal_places=2, max_digits=50, + help_text='avg feedback from those who theyve done work with') follower_count = models.IntegerField(default=0, db_index=True, help_text='how many users follow them') following_count = models.IntegerField(default=0, db_index=True, help_text='how many users are they following') - earnings_count = models.IntegerField(default=0, db_index=True, help_text='How many times has user earned crypto with Gitcoin') - spent_count = models.IntegerField(default=0, db_index=True, help_text='How many times has user spent crypto with Gitcoin') + earnings_count = models.IntegerField( + default=0, db_index=True, help_text='How many times has user earned crypto with Gitcoin') + spent_count = models.IntegerField(default=0, db_index=True, + help_text='How many times has user spent crypto with Gitcoin') sms_verification = models.BooleanField(default=False, help_text=_('SMS verification process')) - validation_attempts = models.PositiveSmallIntegerField(default=0, help_text=_('Number of generated SMS codes to validate account')) - last_validation_request = models.DateTimeField(blank=True, null=True, help_text=_("When the user requested a code for last time ")) - encoded_number = models.CharField(max_length=255, blank=True, help_text=_('Number with the user validate the account')) + validation_attempts = models.PositiveSmallIntegerField( + default=0, help_text=_('Number of generated SMS codes to validate account')) + last_validation_request = models.DateTimeField( + blank=True, null=True, help_text=_("When the user requested a code for last time ")) + encoded_number = models.CharField(max_length=255, blank=True, help_text=_( + 'Number with the user validate the account')) sybil_score = models.IntegerField(default=-1) ignore_tribes = models.ManyToManyField('dashboard.Profile', related_name='ignore', blank=True) objects = ProfileManager() objects_full = ProfileQuerySet.as_manager() - brightid_uuid=models.UUIDField(default=uuid.uuid4, unique=True) - is_brightid_verified=models.BooleanField(default=False) + brightid_uuid = models.UUIDField(default=uuid.uuid4, unique=True) + is_brightid_verified = models.BooleanField(default=False) @property def is_blocked(self): @@ -2900,7 +2923,7 @@ def is_subscription_valid(self): @property def active_subscriptions(self): - if not self.is_org : + if not self.is_org: return TribesSubscription.objects.filter(tribe__in=self.organizations_fk.all()).all() return TribesSubscription.objects.filter(tribe=self).all() @@ -2942,27 +2965,27 @@ def chat_num_unread_msgs(self): chat_driver.login() response = chat_driver.client.make_request('get', - '/users/me/teams/unread', - options=None, - params=None, - data=None, - files=None, - basepath=None) + '/users/me/teams/unread', + options=None, + params=None, + data=None, + files=None, + basepath=None) total_unread = sum(ele.get('msg_count', 0) for ele in response.json()) return total_unread - @property def subscribed_threads(self): - tips = Tip.objects.filter(Q(pk__in=self.received_tips.all()) | Q(pk__in=self.sent_tips.all())).filter(comments_priv__icontains="activity:").all() + tips = Tip.objects.filter(Q(pk__in=self.received_tips.all()) | Q( + pk__in=self.sent_tips.all())).filter(comments_priv__icontains="activity:").all() tips = [tip.comments_priv.split(':')[1] for tip in tips] tips = [ele for ele in tips if ele.isnumeric()] activities = Activity.objects.filter( - Q(pk__in=self.likes.values_list('activity__pk', flat=True)) - | Q(pk__in=self.comments.values_list('activity__pk', flat=True)) - | Q(pk__in=tips) - | Q(profile=self) - | Q(other_profile=self)) + Q(pk__in=self.likes.values_list('activity__pk', flat=True)) + | Q(pk__in=self.comments.values_list('activity__pk', flat=True)) + | Q(pk__in=tips) + | Q(profile=self) + | Q(other_profile=self)) return activities @property @@ -3036,7 +3059,8 @@ def team_or_none_if_timeout(self): try: return self.team except TimeoutError as e: - logger.error(f'timeout for team of {self.handle}; will be fixed when https://github.com/gitcoinco/web/pull/6218/files is in') + logger.error( + f'timeout for team of {self.handle}; will be fixed when https://github.com/gitcoinco/web/pull/6218/files is in') return [] @property @@ -3054,7 +3078,7 @@ def tribe_members(self): @property def ref_code(self): - return hex(self.pk).replace("0x",'') + return hex(self.pk).replace("0x", '') @property def get_org_kudos(self): @@ -3062,7 +3086,7 @@ def get_org_kudos(self): if not self.is_org: return Token.objects.none() - return Token.objects.filter(Q(name__icontains=self.name)|Q(name__icontains=self.handle)).filter(cloned_from_id=F('token_id')).visible() + return Token.objects.filter(Q(name__icontains=self.name) | Q(name__icontains=self.handle)).filter(cloned_from_id=F('token_id')).visible() @property def kudos_authored(self): @@ -3141,7 +3165,6 @@ def get_average_star_rating(self, scale=1): average_rating['total_rating'] = feedbacks.count() return average_rating - @property def get_my_verified_check(self): verification = UserVerificationModel.objects.filter(user=self.user).first() @@ -3194,7 +3217,7 @@ def leaderboard_helper(self, earnings, distinct_on): order_field = f'{distinct_on}__handle' earnings = earnings.filter(network=self.get_network()) leaderboard = earnings.values(order_field).annotate(sum=Sum('value_usd')).annotate(count=Count('value_usd')) - kwargs = {order_field:None} + kwargs = {order_field: None} return [(ele[order_field], ele['count'], ele['sum']) for ele in leaderboard.exclude(**kwargs).order_by('-sum')] @property @@ -3204,8 +3227,10 @@ def bounties(self): for interested in self.interested.all().nocache(): bounties = bounties | Bounty.objects.filter(interested=interested, current_bounty=True) bounties = bounties | Bounty.objects.filter(pk__in=fulfilled_bounty_ids, current_bounty=True) - bounties = bounties | Bounty.objects.filter(bounty_owner_github_username__iexact=self.handle, current_bounty=True) | Bounty.objects.filter(bounty_owner_github_username__iexact="@" + self.handle, current_bounty=True) - bounties = bounties | Bounty.objects.filter(github_url__in=[url for url in self.tips.values_list('github_url', flat=True)], current_bounty=True) + bounties = bounties | Bounty.objects.filter(bounty_owner_github_username__iexact=self.handle, current_bounty=True) | Bounty.objects.filter( + bounty_owner_github_username__iexact="@" + self.handle, current_bounty=True) + bounties = bounties | Bounty.objects.filter( + github_url__in=[url for url in self.tips.values_list('github_url', flat=True)], current_bounty=True) bounties = bounties.distinct() return bounties.order_by('-web3_created') @@ -3250,7 +3275,7 @@ def calculate_all(self): self.num_repeated_relationships = self.calc_num_repeated_relationships() self.avg_hourly_rate = self.calc_avg_hourly_rate() self.success_rate = self.calc_success_rate() - self.reliability = self.calc_reliability_ranking() # must be calc'd last + self.reliability = self.calc_reliability_ranking() # must be calc'd last self.as_dict = json.loads(json.dumps(self.to_dict())) self.as_representation = json.loads(json.dumps(self.to_representation)) self.last_calc_date = timezone.now() + timezone.timedelta(seconds=1) @@ -3325,21 +3350,21 @@ def no_times_slashed_by_staff(self): user_actions = UserAction.objects.filter( profile=self, action='bounty_removed_slashed_by_staff', - ) + ) return user_actions.count() def no_times_been_removed_by_funder(self): user_actions = UserAction.objects.filter( profile=self, action='bounty_removed_by_funder', - ) + ) return user_actions.count() def no_times_been_removed_by_staff(self): user_actions = UserAction.objects.filter( profile=self, action='bounty_removed_by_staff', - ) + ) return user_actions.count() def get_desc(self, funded_bounties, fulfilled_bounties): @@ -3359,7 +3384,6 @@ def get_desc(self, funded_bounties, fulfilled_bounties): return f"@{self.handle} is a {role} who has participated in {total_funded_participated} " \ f"transaction{plural} on Gitcoin" - @property def desc(self): bounties1 = self.sent_earnings if not self.is_org else Earning.objects.none() @@ -3450,7 +3474,6 @@ def calc_activity_level(self): return "Med" return "Low" - def calc_longest_streak(self): """ Determines the longest streak, in workdays, of this user @@ -3462,8 +3485,10 @@ def calc_longest_streak(self): # setup action_dates = self.actions.all().values_list('created_on', flat=True) action_dates = set([ele.replace(tzinfo=pytz.utc).strftime('%m/%d/%Y') for ele in action_dates]) - start_date = timezone.datetime(self.created_on.year, self.created_on.month, self.created_on.day).replace(tzinfo=pytz.utc) - end_date = timezone.datetime(timezone.now().year, timezone.now().month, timezone.now().day).replace(tzinfo=pytz.utc) + start_date = timezone.datetime(self.created_on.year, self.created_on.month, + self.created_on.day).replace(tzinfo=pytz.utc) + end_date = timezone.datetime(timezone.now().year, timezone.now().month, + timezone.now().day).replace(tzinfo=pytz.utc) # loop setup iterdate = start_date @@ -3499,7 +3524,7 @@ def calc_num_repeated_relationships(self): relationships += list(self.sent_earnings.values_list('to_profile__handle', flat=True)) relationships += list(self.earnings.values_list('from_profile__handle', flat=True)) - rel_count = { key: 0 for key in relationships } + rel_count = {key: 0 for key in relationships} for rel in relationships: rel_count[rel] += 1 @@ -3527,7 +3552,8 @@ def calc_success_rate(self): int; the success percentage for this users bounties as a positive integer. """ - bounties = self.bounties.filter(network=self.get_network()) if self.cascaded_persona == 'hunter' else self.get_sent_bounties.current() + bounties = self.bounties.filter(network=self.get_network( + )) if self.cascaded_persona == 'hunter' else self.get_sent_bounties.current() completed_bounties = bounties.filter(idx_status='done').count() expired_bounties = bounties.filter(idx_status='expired').count() cancelled_bounties = bounties.filter(idx_status='cancelled').count() @@ -3564,22 +3590,21 @@ def calc_reliability_ranking(self): base_rating = 0 deductions = 0 - - #calculate base rating + # calculate base rating num_earnings = self.earnings.count() + self.sent_earnings.count() if num_earnings < 2: return "Unproven" if num_earnings > high_threshold: - base_rating = 3 # high + base_rating = 3 # high elif num_earnings > med_threshold: - base_rating = 2 # medium + base_rating = 2 # medium else: - base_rating = 1 # low + base_rating = 1 # low # calculate deductions - ## ratings deduction + # ratings deduction num_5_star_ratings = self.feedbacks_got.filter(rating=5).count() num_subpar_star_ratings = self.feedbacks_got.filter(rating__lt=4).count() total_rating = num_subpar_star_ratings + num_5_star_ratings @@ -3589,28 +3614,29 @@ def calc_reliability_ranking(self): if num_5_star_ratings/total_rating > rating_merit_threshold: deductions += 1 - ## abandonment deduction - total_removals = self.no_times_been_removed_by_funder() + self.no_times_been_removed_by_staff()+ (self.no_times_slashed_by_staff() * abandon_slash_multiplier) + # abandonment deduction + total_removals = self.no_times_been_removed_by_funder() + self.no_times_been_removed_by_staff() + \ + (self.no_times_slashed_by_staff() * abandon_slash_multiplier) if total_rating: if total_removals/num_earnings < abandon_deduction_threshold: deductions -= 1 if num_earnings > abandon_merit_earnings_threshold and total_removals/num_earnings > abandon_merit_threshold: deductions += 1 - ## success rate deduction + # success rate deduction if self.success_rate != -1: if self.success_rate < success_rate_deduction_threshold: deductions -= 1 if self.success_rate > success_ratemerit_threshold: deductions += 1 - ## activity level deduction + # activity level deduction if self.activity_level == "High": - deductions += 1 + deductions += 1 - ## activity level deduction + # activity level deduction if self.num_repeated_relationships > num_repeated_relationships_merit_threshold: - deductions += 1 + deductions += 1 # calculate final rating final_rating = base_rating + deductions @@ -3639,7 +3665,6 @@ def completed_bounties(self): return self.bounties.filter( idx_status__in=['done'], network=network).count() - @property def get_quarterly_stats(self): """Generate last 90 days stats for this user. @@ -3778,10 +3803,10 @@ def get_quarterly_stats(self): relevant_bounties = Bounty.objects.none() for keyword in user_coding_languages: relevant_bounties = relevant_bounties.union(potential_bounties.current().filter( - network=Profile.get_network(), - metadata__icontains=keyword, - idx_status__in=['open'], - ).order_by('?') + network=Profile.get_network(), + metadata__icontains=keyword, + idx_status__in=['open'], + ).order_by('?') ) relevant_bounties = relevant_bounties[:3] relevant_bounties = list(relevant_bounties) @@ -3884,7 +3909,6 @@ def name(self): return self.data["name"] return self.username - def is_github_token_valid(self): """Check whether or not a Github OAuth token is valid. @@ -3913,7 +3937,7 @@ def __str__(self): return self.handle def get_relative_url(self, preceding_slash=True): - from dashboard.utils import get_url_first_indexes # avoid circular import + from dashboard.utils import get_url_first_indexes # avoid circular import prefix = '' if self.handle in get_url_first_indexes(): # handle collision @@ -3994,7 +4018,6 @@ def update_slack_integration(self, token, channel, repos): self.slack_channel = channel self.save() - @staticmethod def get_network(): if settings.OVERRIDE_NETWORK: @@ -4104,7 +4127,8 @@ def get_all_tokens_sum(self, sum_type='collected', network='mainnet', bounties=N all_tokens_sum_tmp = {token: 0 for token in set([ele[0] for ele in tokens_and_values])} for ele in tokens_and_values: all_tokens_sum_tmp[ele[0]] += ele[1] / 10**18 - all_tokens_sum = [{'token_name': token_name, 'value_in_token': float(value_in_token)} for token_name, value_in_token in all_tokens_sum_tmp.items()] + all_tokens_sum = [{'token_name': token_name, 'value_in_token': float( + value_in_token)} for token_name, value_in_token in all_tokens_sum_tmp.items()] except Exception as e: logger.exception(e) @@ -4211,7 +4235,6 @@ def to_representation(instance): 'total_earned': instance.get_eth_sum(network=None) } - def to_es(self): return json.dumps(self.to_dict()) @@ -4266,7 +4289,8 @@ def to_dict(self): total_funded = funded_bounties.count() total_fulfilled = fulfilled_bounties.count() + self.tips.count() desc = self.desc - no_times_been_removed = self.no_times_been_removed_by_funder() + self.no_times_been_removed_by_staff() + self.no_times_slashed_by_staff() + no_times_been_removed = self.no_times_been_removed_by_funder() + self.no_times_been_removed_by_staff() + \ + self.no_times_slashed_by_staff() org_works_with = [] if self.is_org: org_bounties = self.get_orgs_bounties(network='mainnet') @@ -4300,9 +4324,11 @@ def to_dict(self): if self.cascaded_persona == 'org': active_bounties = self.bounties.filter(idx_status__in=Bounty.WORK_IN_PROGRESS_STATUSES, network='mainnet') elif self.cascaded_persona == 'funder': - active_bounties = active_bounties = Bounty.objects.filter(bounty_owner_profile=self, idx_status__in=Bounty.WORK_IN_PROGRESS_STATUSES, network='mainnet', current_bounty=True) + active_bounties = active_bounties = Bounty.objects.filter( + bounty_owner_profile=self, idx_status__in=Bounty.WORK_IN_PROGRESS_STATUSES, network='mainnet', current_bounty=True) elif self.cascaded_persona == 'hunter': - active_bounties = Bounty.objects.filter(pk__in=self.active_bounties.filter(pending=False).values_list('bounty', flat=True), network='mainnet') + active_bounties = Bounty.objects.filter(pk__in=self.active_bounties.filter( + pending=False).values_list('bounty', flat=True), network='mainnet') else: active_bounties = Bounty.objects.none() params['active_bounties'] = list(active_bounties.values_list('pk', flat=True)) @@ -4313,7 +4339,8 @@ def to_dict(self): if not all_activities or all_activities.count() == 0: params['none'] = True else: - counts = all_activities.values('activity_type').order_by('activity_type').annotate(the_count=Count('activity_type')) + counts = all_activities.values('activity_type').order_by( + 'activity_type').annotate(the_count=Count('activity_type')) counts = {ele['activity_type']: ele['the_count'] for ele in counts} params['activities_counts'] = counts @@ -4332,11 +4359,13 @@ def to_dict(self): context['verification'] = bool(profile.get_my_verified_check) context['avg_rating'] = profile.get_average_star_rating() context['suppress_sumo'] = True - context['total_kudos_count'] = profile.get_my_kudos.count() + profile.get_sent_kudos.count() + profile.get_org_kudos.count() + context['total_kudos_count'] = profile.get_my_kudos.count() + profile.get_sent_kudos.count() + \ + profile.get_org_kudos.count() context['total_kudos_sent_count'] = profile.sent_kudos.count() context['total_kudos_received_count'] = profile.received_kudos.count() context['total_grant_created'] = profile.grant_admin.count() - context['total_grant_contributions'] = profile.grant_contributor.filter(subscription_contribution__success=True).values_list('subscription_contribution').count() + profile.grant_phantom_funding.count() + context['total_grant_contributions'] = profile.grant_contributor.filter(subscription_contribution__success=True).values_list( + 'subscription_contribution').count() + profile.grant_phantom_funding.count() context['total_grant_actions'] = context['total_grant_created'] + context['total_grant_contributions'] context['total_tips_sent'] = profile.get_sent_tips.count() @@ -4354,7 +4383,8 @@ def to_dict(self): if keyword not in portfolio_keywords.keys(): portfolio_keywords[keyword] = 0 portfolio_keywords[keyword] += 1 - sorted_portfolio_keywords = [(k, portfolio_keywords[k]) for k in sorted(portfolio_keywords, key=portfolio_keywords.get, reverse=True)] + sorted_portfolio_keywords = [(k, portfolio_keywords[k]) + for k in sorted(portfolio_keywords, key=portfolio_keywords.get, reverse=True)] context['portfolio'] = list(portfolio_bounties.values_list('pk', flat=True)) context['portfolio_keywords'] = sorted_portfolio_keywords @@ -4366,7 +4396,8 @@ def to_dict(self): context['spent_total'] = round(sum(earnings_from.values_list('value_usd', flat=True))) context['earnings_count'] = earnings_to.count() context['spent_count'] = earnings_from.count() - context['hackathons_participated_in'] = self.interested.filter(bounty__event__isnull=False).distinct('bounty__event').count() + context['hackathons_participated_in'] = self.interested.filter( + bounty__event__isnull=False).distinct('bounty__event').count() context['hackathons_funded'] = funded_bounties.filter(event__isnull=False).distinct('event').count() if context['earnings_total'] > 1000: context['earnings_total'] = f"{round(context['earnings_total']/1000)}k" @@ -4374,8 +4405,6 @@ def to_dict(self): context['spent_total'] = f"{round(context['spent_total']/1000)}k" return context - - @property def reassemble_profile_dict(self): params = self.as_dict @@ -4389,8 +4418,6 @@ def reassemble_profile_dict(self): params['portfolio'] = BountyFulfillment.objects.filter(pk__in=params.get('portfolio', [])) return params - - @property def locations(self): from app.utils import get_location_from_ip @@ -4429,7 +4456,7 @@ def psave_profile(sender, instance, **kwargs): # sync organizations_fk and organizations if hasattr(instance, 'pk') and instance.pk: for handle in instance.organizations: - handle =handle.lower() + handle = handle.lower() if not instance.organizations_fk.filter(handle=handle).exists(): obj = Profile.objects.filter(handle=handle).first() if obj: @@ -4455,15 +4482,16 @@ def psave_profile(sender, instance, **kwargs): source_type=ContentType.objects.get(app_label='dashboard', model='profile'), source_id=instance.pk, defaults={ - "created_on":instance.created_on, - "title":instance.handle, - "description":instance.desc, - "url":instance.url, - "visible_to":None, + "created_on": instance.created_on, + "title": instance.handle, + "description": instance.desc, + "url": instance.url, + "visible_to": None, 'img_url': instance.avatar_url, } ) + @receiver(user_logged_in) def post_login(sender, request, user, **kwargs): """Handle actions to take on user login.""" @@ -4480,13 +4508,16 @@ def post_logout(sender, request, user, **kwargs): from dashboard.utils import create_user_action create_user_action(user, 'Logout', request) + class UserDirectoryQuerySet(models.QuerySet): """Define the Profile QuerySet to be used as the objects manager.""" + class UserDirectoryManager(models.Manager): def get_queryset(self): return UserDirectoryQuerySet(self.model, using=self._db) + class UserDirectory(models.Model): profile_id = models.CharField(max_length=255, primary_key=True) join_date = models.CharField(null=True, max_length=255) @@ -4531,7 +4562,7 @@ class UserDirectory(models.Model): submitted_bug = models.IntegerField() submitted_other = models.IntegerField() submitted_improvement = models.IntegerField() - bounty_earnings = models.FloatField() + bounty_earnings = models.FloatField() bounty_work_start_orgs = ArrayField(base_field=models.CharField(max_length=255), null=True) bounty_work_submit_orgs = ArrayField(base_field=models.CharField(max_length=255), null=True) kudos_sends = models.IntegerField() @@ -4553,22 +4584,21 @@ class UserDirectory(models.Model): earnings_count = models.FloatField() follower_count = models.IntegerField() following_count = models.IntegerField() - num_repeated_relationships = models.IntegerField() + num_repeated_relationships = models.IntegerField() verification_status = models.CharField(null=True, max_length=255) - def email_if_not_supressed(self): is_on_global_suppression_list = EmailSupressionList.objects.filter(email__iexact=self.email).exists() if is_on_global_suppression_list: return '' return self.email - objects = UserDirectoryManager() class Meta: managed = False + class ProfileSerializer(serializers.BaseSerializer): """Handle serializing the Profile object.""" @@ -4595,6 +4625,7 @@ def to_representation(self, instance): instance.save() return instance.as_representation + @receiver(pre_save, sender=Tip, dispatch_uid="normalize_tip_usernames") def normalize_tip_usernames(sender, instance, **kwargs): """Handle pre-save signals from Tips to normalize Github usernames.""" @@ -4620,7 +4651,8 @@ class UserAction(SuperModel): ] action = models.CharField(max_length=50, choices=ACTION_TYPES, db_index=True) user = models.ForeignKey(User, related_name='actions', on_delete=models.SET_NULL, null=True, db_index=True) - profile = models.ForeignKey('dashboard.Profile', related_name='actions', on_delete=models.CASCADE, null=True, db_index=True) + profile = models.ForeignKey('dashboard.Profile', related_name='actions', + on_delete=models.CASCADE, null=True, db_index=True) ip_address = models.GenericIPAddressField(null=True, db_index=True) location_data = JSONField(default=dict) metadata = JSONField(default=dict) @@ -4650,6 +4682,7 @@ def post_add_ua(sender, instance, created, **kwargs): if created: pass + class CoinRedemption(SuperModel): """Define the coin redemption schema.""" @@ -4881,14 +4914,19 @@ class HackathonEvent(SuperModel): start_date = models.DateTimeField() end_date = models.DateTimeField() banner = models.ImageField(null=True, blank=True) - background_color = models.CharField(max_length=255, null=True, blank=True, help_text='hexcode for the banner, default to white') - text_color = models.CharField(max_length=255, null=True, blank=True, help_text='hexcode for the text, default to black') - border_color = models.CharField(max_length=255, null=True, blank=True, help_text='hexcode for the border, default to none') + background_color = models.CharField(max_length=255, null=True, blank=True, + help_text='hexcode for the banner, default to white') + text_color = models.CharField(max_length=255, null=True, blank=True, + help_text='hexcode for the text, default to black') + border_color = models.CharField(max_length=255, null=True, blank=True, + help_text='hexcode for the border, default to none') identifier = models.CharField(max_length=255, default='', help_text='used for custom styling for the banner') sponsors = models.ManyToManyField(Sponsor, through='HackathonSponsor') - sponsor_profiles = models.ManyToManyField('dashboard.Profile', blank=True, limit_choices_to={'data__type': 'Organization'}) + sponsor_profiles = models.ManyToManyField('dashboard.Profile', blank=True, + limit_choices_to={'data__type': 'Organization'}) show_results = models.BooleanField(help_text=_('Hide/Show the links to access hackathon results'), default=True) - hackathon_summary = models.CharField(max_length=280, blank=True, help_text=_('280 char summary that shows up on hackathon cards on the hackathon list page')) + hackathon_summary = models.CharField(max_length=280, blank=True, help_text=_( + '280 char summary that shows up on hackathon cards on the hackathon list page')) description = models.TextField(default='', blank=True, help_text=_('HTML rich description.')) quest_link = models.CharField(max_length=255, blank=True) chat_channel_id = models.CharField(max_length=255, blank=True, null=True) @@ -4991,15 +5029,14 @@ def psave_hackathonevent(sender, instance, **kwargs): source_type=ContentType.objects.get(app_label='dashboard', model='hackathonevent'), source_id=instance.pk, defaults={ - "created_on":instance.created_on, - "title":instance.name, - "description":instance.stats['range'], - "url":instance.onboard_url, - "visible_to":None, + "created_on": instance.created_on, + "title": instance.name, + "description": instance.stats['range'], + "url": instance.onboard_url, + "visible_to": None, 'img_url': instance.logo.url if instance.logo else None, } - ) - + ) class HackathonSponsor(SuperModel): @@ -5184,7 +5221,7 @@ def anonymized_comment(self): import re replace_str = [ self.bounty.bounty_owner_github_username, - ] + ] for profile in [self.sender_profile, self.receiver_profile, self.bounty.org_profile]: if profile: replace_str.append(profile.handle) @@ -5215,7 +5252,8 @@ class ProfileView(SuperModel): """Records profileviews .""" target = models.ForeignKey('dashboard.Profile', related_name='viewed_by', on_delete=models.CASCADE, db_index=True) - viewer = models.ForeignKey('dashboard.Profile', related_name='viewed_profiles', on_delete=models.CASCADE, db_index=True) + viewer = models.ForeignKey('dashboard.Profile', related_name='viewed_profiles', + on_delete=models.CASCADE, db_index=True) class Meta: ordering = ['-pk'] @@ -5240,9 +5278,12 @@ def post_add_profileview(sender, instance, created, **kwargs): class Earning(SuperModel): """Records Earning - the generic object for all earnings on the platform .""" - from_profile = models.ForeignKey('dashboard.Profile', related_name='sent_earnings', on_delete=models.CASCADE, db_index=True, null=True) - to_profile = models.ForeignKey('dashboard.Profile', related_name='earnings', on_delete=models.CASCADE, db_index=True, null=True) - org_profile = models.ForeignKey('dashboard.Profile', related_name='org_earnings', on_delete=models.CASCADE, db_index=True, null=True, blank=True) + from_profile = models.ForeignKey('dashboard.Profile', related_name='sent_earnings', + on_delete=models.CASCADE, db_index=True, null=True) + to_profile = models.ForeignKey('dashboard.Profile', related_name='earnings', + on_delete=models.CASCADE, db_index=True, null=True) + org_profile = models.ForeignKey('dashboard.Profile', related_name='org_earnings', + on_delete=models.CASCADE, db_index=True, null=True, blank=True) value_usd = models.DecimalField(decimal_places=2, max_digits=50, null=True) source_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) source_id = models.PositiveIntegerField(db_index=True) @@ -5261,7 +5302,6 @@ def source_type_human(self): source_type = 'grant' return source_type - def __str__(self): return f"{self.from_profile} => {self.to_profile} of ${self.value_usd} on {self.created_on} for {self.source}" @@ -5279,13 +5319,14 @@ def create_auto_follow(self): TribeMember.objects.update_or_create( profile=p1, org=p2, - defaults={'why':'auto'} - ) + defaults={'why': 'auto'} + ) count += 1 except: pass return count + @receiver(post_save, sender=Earning, dispatch_uid="post_save_earning") def post_save_earning(sender, instance, created, **kwargs): if created: @@ -5295,6 +5336,7 @@ def post_save_earning(sender, instance, created, **kwargs): if instance.txid: watch_txn(instance.txid) + def get_my_earnings_counter_profiles(profile_pk): # returns profiles that a user has done business with from_profile_earnings = Earning.objects.filter(from_profile=profile_pk) @@ -5303,7 +5345,8 @@ def get_my_earnings_counter_profiles(profile_pk): from_profile_earnings = list(from_profile_earnings.values_list('to_profile', flat=True)) to_profile_earnings = list(to_profile_earnings.values_list('from_profile', flat=True)) - org_profile_earnings = list(org_profile_earnings.values_list('from_profile', flat=True)) + list(org_profile_earnings.values_list('to_profile', flat=True)) + org_profile_earnings = list(org_profile_earnings.values_list('from_profile', flat=True)) + \ + list(org_profile_earnings.values_list('to_profile', flat=True)) all_earnings = from_profile_earnings + to_profile_earnings + org_profile_earnings return all_earnings @@ -5324,7 +5367,8 @@ class PortfolioItem(SuperModel): title = models.CharField(max_length=255) tags = ArrayField(models.CharField(max_length=50), default=list, blank=True) link = models.URLField(null=True) - profile = models.ForeignKey('dashboard.Profile', related_name='portfolio_items', on_delete=models.CASCADE, db_index=True) + profile = models.ForeignKey('dashboard.Profile', related_name='portfolio_items', + on_delete=models.CASCADE, db_index=True) def __str__(self): return f"{self.title} by {self.profile.handle}" @@ -5347,7 +5391,7 @@ class TribeMember(SuperModel): ('pending', 'pending'), ('rejected', 'rejected'), ] - #from + # from profile = models.ForeignKey('dashboard.Profile', related_name='follower', on_delete=models.CASCADE) # to org = models.ForeignKey('dashboard.Profile', related_name='org', on_delete=models.CASCADE, null=True, blank=True) @@ -5540,9 +5584,11 @@ def investigate_sybil(instance): else: htmls.append('SMS Verified') - htmls += [f"View Recent User Actions
"] + htmls += [ + f"View Recent User Actions
"] - htmls += [ f"Github Created: {instance.github_created_on.strftime('%Y-%m-%d')} ({naturaltime(instance.github_created_on)})
"] + htmls += [ + f"Github Created: {instance.github_created_on.strftime('%Y-%m-%d')} ({naturaltime(instance.github_created_on)})
"] if instance.github_created_on > (timezone.now() - timezone.timedelta(days=7)): htmls.append('New Github (DING)') @@ -5558,7 +5604,8 @@ def investigate_sybil(instance): if instance.preferred_payout_address: htmls.append('Preferred Payout Address') htmls.append(f' - {instance.preferred_payout_address}') - other_Profiles = Profile.objects.filter(preferred_payout_address=instance.preferred_payout_address).exclude(pk=instance.pk).values_list('handle', flat=True) + other_Profiles = Profile.objects.filter(preferred_payout_address=instance.preferred_payout_address).exclude( + pk=instance.pk).values_list('handle', flat=True) if other_Profiles.count() > 0: total_sybil_score += 1 htmls.append('Other Profiles with this addr (DING)') @@ -5567,7 +5614,8 @@ def investigate_sybil(instance): htmls.append('Many other Profiles with this addr (DING)') url = f'/_administrationdashboard/profile/?preferred_payout_address={instance.preferred_payout_address}' _other_Profiles = [f'{handle}' for handle in other_Profiles] - htmls += [f" -- {len(_other_Profiles)} other profiles share this ppa: {', '.join(list(_other_Profiles))}
"] + htmls += [ + f" -- {len(_other_Profiles)} other profiles share this ppa: {', '.join(list(_other_Profiles))}
"] print(f" - ip ({len(ipAddresses)}) {time.time()}") htmls.append('IP Addresses') @@ -5575,7 +5623,8 @@ def investigate_sybil(instance): ip_flagged = False for ip in ipAddresses: html = f"- {ip['ip_address']} ({ip['id__count']} Visits)" - other_Profiles = UserAction.objects.filter(ip_address=ip['ip_address'], profile__isnull=False).exclude(profile=instance).distinct('profile').values_list('profile__handle', flat=True) + other_Profiles = UserAction.objects.filter(ip_address=ip['ip_address'], profile__isnull=False).exclude( + profile=instance).distinct('profile').values_list('profile__handle', flat=True) if len(other_Profiles): _other_Profiles = [f'{handle}' for handle in other_Profiles] html += f"
-- {len(_other_Profiles)} other profiles share this IP: {', '.join(list(_other_Profiles))}" @@ -5603,7 +5652,7 @@ def investigate_sybil(instance): for earning in earnings: html = f"- {earning}" htmls.append(html) - #TODO: if shared with a known sybil, then total_sybil_score += 1 + # TODO: if shared with a known sybil, then total_sybil_score += 1 htmls.append("'") sent_earnings = instance.sent_earnings.filter(network='mainnet').all() @@ -5659,7 +5708,6 @@ class ProfileVerification(SuperModel): validation_comment = models.CharField(max_length=255, null=True, blank=True) success = models.BooleanField(help_text=_('Was a successful transaction verification?'), default=False) - def __str__(self): return f'{self.phone_number} ({self.caller_type}) from {self.country_code} request ${self.delivery_method} code at {self.created_on}' diff --git a/app/dashboard/templates/profiles/tab_portfolio.html b/app/dashboard/templates/profiles/tab_portfolio.html index 7c186395e73..6a9d885aa2f 100644 --- a/app/dashboard/templates/profiles/tab_portfolio.html +++ b/app/dashboard/templates/profiles/tab_portfolio.html @@ -97,6 +97,11 @@
{{pi.title}}
  {% if pi.link %} View Work > {% endif %} +
+ {% csrf_token %} + + +
{% endfor %} diff --git a/app/dashboard/views.py b/app/dashboard/views.py index 3716070ac2f..9613835ee20 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -2821,6 +2821,7 @@ def get_profile_tab(request, profile, tab, prev_context): pass elif tab == 'portfolio': title = request.POST.get('project_title') + pk = request.POST.get('project_pk') if title: if request.POST.get('URL')[0:4] != "http": messages.error(request, 'Invalid link.') @@ -2836,6 +2837,14 @@ def get_profile_tab(request, profile, tab, prev_context): tags=request.POST.get('tags').split(','), ) messages.info(request, 'Portfolio Item added.') + if pk: + # delete portfolio item + if not request.user.is_authenticated or request.user.profile.pk != profile.pk: + messages.error(request, 'Not Authorized') + pi = PortfolioItem.objects.filter(pk=pk).first() + if pi: + pi.delete() + messages.info(request, 'Portfolio Item has been deleted.') elif tab == 'earnings': context['earnings'] = Earning.objects.filter(to_profile=profile, network='mainnet', value_usd__isnull=False).order_by('-created_on') elif tab == 'spent': From 097d6da36c126be0663669a115ce52c7eeab1704 Mon Sep 17 00:00:00 2001 From: Chibuotu Amadi Date: Thu, 24 Sep 2020 04:03:15 +0100 Subject: [PATCH 2/3] fix(docs): little updates --- docs/CONTRIBUTING.md | 2 +- docs/GRANTS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 149bbb008af..8b03c303458 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -123,7 +123,7 @@ life easier for you if you are the kind who enjoys multiple things parallely. These should also be created directly off of the `master` branch. ```shell -git checkout -b my-branch -t upstream/master +git checkout -b my-branch ``` ### Step 3: Code diff --git a/docs/GRANTS.md b/docs/GRANTS.md index 106a9f780ba..f345e134059 100644 --- a/docs/GRANTS.md +++ b/docs/GRANTS.md @@ -23,7 +23,7 @@ can use this account to send transfers without prompting you for each transfer! only held by this account temporarily to improve UX. Because it does not permanently hold funds there is no additional security risk. -### Checkout Fow +### Checkout Flow Based on the items in your cart, we check your zkSync balances to see if you already have enough funds on zkSync to complete checkout. From b69604f2705e2d504f062fe23881541818f47b2b Mon Sep 17 00:00:00 2001 From: Chibuotu Amadi Date: Thu, 24 Sep 2020 04:22:53 +0100 Subject: [PATCH 3/3] fix(tests): resolve import error --- app/marketing/management/commands/new_bounties_email.py | 6 ++++-- .../tests/management/commands/test_new_bounties_email.py | 9 ++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/marketing/management/commands/new_bounties_email.py b/app/marketing/management/commands/new_bounties_email.py index c42a95ddc24..a30a8759e70 100644 --- a/app/marketing/management/commands/new_bounties_email.py +++ b/app/marketing/management/commands/new_bounties_email.py @@ -34,6 +34,7 @@ THROTTLE_S = 0.005 + class Command(BaseCommand): help = 'sends new_bounty_daily _emails' @@ -63,7 +64,8 @@ def handle(self, *args, **options): # stats speed = counter_total / (time.time() - start_time) ETA = round((total_count - counter_total) / speed / 3600, 1) - print(f"{counter_sent} sent/{counter_total} enabled/ {total_count} total, {round(speed, 2)}/s, ETA:{ETA}h, working on {to_email} ") + print( + f"{counter_sent} sent/{counter_total} enabled/ {total_count} total, {round(speed, 2)}/s, ETA:{ETA}h, working on {to_email} ") # send did_send = new_bounty_daily(es.pk) @@ -71,7 +73,7 @@ def handle(self, *args, **options): counter_sent += 1 time.sleep(THROTTLE_S) - + except Exception as e: logging.exception(e) print(e) diff --git a/app/marketing/tests/management/commands/test_new_bounties_email.py b/app/marketing/tests/management/commands/test_new_bounties_email.py index ec40375ae7a..845db3c1b97 100644 --- a/app/marketing/tests/management/commands/test_new_bounties_email.py +++ b/app/marketing/tests/management/commands/test_new_bounties_email.py @@ -5,7 +5,7 @@ import pytest from dashboard.models import Bounty, Profile -from marketing.management.commands.new_bounties_email import get_bounties_for_keywords +from marketing.mails import get_bounties_for_keywords from marketing.models import Keyword from test_plus.test import TestCase @@ -13,10 +13,10 @@ @pytest.mark.django_db class TestNewBountiesEmail(TestCase): """Define tests for testing new bounties email.""" - + def setUp(self): """Perform setup for the testcase.""" - + Profile.objects.create( data={}, handle='fred', @@ -69,8 +69,7 @@ def setUp(self): network='mainnet', ) - def test_get_bounties_for_keywords(self): """Test get_bounties_for_keywords function to confirm a bounty reserved for a specific user is excluded.""" - new_bounties, _all_bounties = get_bounties_for_keywords('Python',24) + new_bounties, _all_bounties = get_bounties_for_keywords('Python', 24) assert new_bounties.count() == 1