Skip to content

Commit

Permalink
adds view counts to many objects on site, so we can see what items ar…
Browse files Browse the repository at this point in the history
…e popular when (#6692)

* adds view counts to many objects on site, so we can see what items are popular when

* make fix

* adds time series and user specific view data

* make fix

* recreate mig

Co-authored-by: Aditya Anand M C <[email protected]>
Co-authored-by: Dan Lipert <[email protected]>
  • Loading branch information
3 people authored May 27, 2020
1 parent e89d51f commit 5c21326
Show file tree
Hide file tree
Showing 15 changed files with 169 additions and 13 deletions.
20 changes: 16 additions & 4 deletions app/dashboard/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
Activity, Answer, BlockedURLFilter, BlockedUser, Bounty, BountyEvent, BountyFulfillment, BountyInvites,
BountySyncRequest, CoinRedemption, CoinRedemptionRequest, Coupon, Earning, FeedbackEntry, FundRequest,
HackathonEvent, HackathonProject, HackathonRegistration, HackathonSponsor, Interest, Investigation, LabsResearch,
Option, Poll, PortfolioItem, Profile, ProfileView, Question, SearchHistory, Sponsor, Tip, TipPayout, TokenApproval,
TribeMember, UserAction, UserVerificationModel,
ObjectView, Option, Poll, PortfolioItem, Profile, ProfileView, Question, SearchHistory, Sponsor, Tip, TipPayout,
TokenApproval, TribeMember, UserAction, UserVerificationModel,
)


Expand All @@ -53,6 +53,11 @@ class GeneralAdmin(admin.ModelAdmin):
list_display = ['created_on', '__str__']


class ObjectViewAdmin(admin.ModelAdmin):
ordering = ['-id']
list_display = ['created_on', '__str__']
raw_id_fields = ['viewer']

class InvestigationAdmin(admin.ModelAdmin):
ordering = ['-id']
list_display = ['created_on', '__str__']
Expand Down Expand Up @@ -267,7 +272,7 @@ class BountyAdmin(admin.ModelAdmin):
list_display = ['pk', 'img', 'bounty_state', 'idx_status', 'network_link', 'standard_bounties_id_link', 'bounty_link', 'what']
readonly_fields = [
'what', 'img', 'fulfillments_link', 'standard_bounties_id_link', 'bounty_link', 'network_link',
'_action_urls', 'coupon_link'
'_action_urls', 'coupon_link', 'view_count'
]

def img(self, instance):
Expand All @@ -280,6 +285,9 @@ def img(self, instance):
def what(self, instance):
return str(instance)

def view_count(self, instance):
return instance.get_view_count

def fulfillments_link(self, instance):
copy = f'fulfillments({instance.num_fulfillments})'
url = f'/_administrationdashboard/bountyfulfillment/?bounty={instance.pk}'
Expand Down Expand Up @@ -342,7 +350,10 @@ class HackathonEventAdmin(admin.ModelAdmin):
raw_id_fields = ['sponsor_profiles']
list_display = ['pk', 'img', 'name', 'start_date', 'end_date', 'explorer_link']
list_filter = ('sponsor_profiles', )
readonly_fields = ['img', 'explorer_link', 'stats']
readonly_fields = ['img', 'explorer_link', 'stats', 'view_count']

def view_count(self, instance):
return instance.get_view_count

def img(self, instance):
"""Returns a formatted HTML img node or 'n/a' if the HackathonEvent has no logo.
Expand Down Expand Up @@ -500,5 +511,6 @@ class AnswersAdmin(admin.ModelAdmin):
admin.site.register(FundRequest, FundRequestAdmin)
admin.site.register(Poll, PollsAdmin)
admin.site.register(Question, QuestionsAdmin)
admin.site.register(ObjectView, ObjectViewAdmin)
admin.site.register(Option, OptionsAdmin)
admin.site.register(Answer, AnswersAdmin)
3 changes: 3 additions & 0 deletions app/dashboard/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,9 @@ def merge_bounty(latest_old_bounty, new_bounty, metadata, bounty_details, verbos
except Exception as e:
logger.error(e)

if latest_old_bounty:
new_bounty.set_view_count(latest_old_bounty.view_count)

if latest_old_bounty and latest_old_bounty.event:
new_bounty.event = latest_old_bounty.event
new_bounty.save()
Expand Down
33 changes: 33 additions & 0 deletions app/dashboard/migrations/0118_objectview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 2.2.4 on 2020-05-27 14:18

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import economy.models


class Migration(migrations.Migration):

dependencies = [
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('dashboard', '0117_auto_20200527_1413'),
]

operations = [
migrations.CreateModel(
name='ObjectView',
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)),
('target_id', models.PositiveIntegerField(db_index=True)),
('view_type', models.CharField(max_length=255)),
('target_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
('viewer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='objectviews', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-pk'],
},
),
]
16 changes: 16 additions & 0 deletions app/dashboard/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5083,3 +5083,19 @@ def investigate_sybil(instance):
description=htmls,
key='sybil',
)


class ObjectView(SuperModel):
"""Records object views ."""
viewer = models.ForeignKey(User, related_name='objectviews', on_delete=models.SET_NULL, null=True, db_index=True)
target_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
target_id = models.PositiveIntegerField(db_index=True)
target = GenericForeignKey('target_type', 'target_id')
view_type = models.CharField(max_length=255)

class Meta:
ordering = ['-pk']

def __str__(self):
return f"{self.viewer} => {self.target} on {self.created_on}"

6 changes: 6 additions & 0 deletions app/dashboard/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
Activity, Bounty, BountyFulfillment, BountyInvites, BountyRequest, HackathonEvent, HackathonProject, Interest,
Profile, ProfileSerializer, SearchHistory, TribeMember,
)
from .tasks import increment_view_count

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -451,6 +452,11 @@ def get_queryset(self):
logger.debug(e)
pass

# increment view counts
pks = [ele.pk for ele in queryset]
if len(pks):
view_type = 'individual' if len(pks) == 1 else 'list'
increment_view_count.delay(pks, queryset[0].content_type, request.user.id, view_type)

return queryset

Expand Down
32 changes: 31 additions & 1 deletion app/dashboard/tasks.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType

from app.redis_service import RedisService
from celery import app, group
from celery.utils.log import get_task_logger
from chat.tasks import create_channel
from dashboard.models import Activity, Bounty, Profile
from dashboard.models import Activity, Bounty, ObjectView, Profile
from marketing.mails import func_name, grant_update_email, send_mail
from retail.emails import render_share_bounty

Expand Down Expand Up @@ -135,3 +137,31 @@ def m2m_changed_interested(self, bounty_pk, retry: bool = True) -> None:
from dashboard.notifications import maybe_market_to_github
maybe_market_to_github(bounty, 'work_started',
profile_pairs=bounty.profile_pairs)



@app.shared_task(bind=True, max_retries=1)
def increment_view_count(self, pks, content_type, user_id, view_type, retry: bool = True) -> None:
"""
:param self:
:param pk:
:param content_type:
:param user_id:
:param view_type:
:return:
"""
user = None
if user_id:
user = User.objects.get(pk=user_id)
redis = RedisService().redis
for pk in pks:
key = f"{content_type}_{pk}"
print(key)
result = redis.incr(key)

ObjectView.objects.create(
viewer=user,
target_id=pk,
target_type=ContentType.objects.filter(model=content_type).first(),
view_type=view_type,
)
7 changes: 7 additions & 0 deletions app/dashboard/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
add_to_channel, associate_chat_to_profile, chat_notify_default_props, create_channel_if_not_exists,
)
from dashboard.context import quickstart as qs
from dashboard.tasks import increment_view_count
from dashboard.utils import (
ProfileHiddenException, ProfileNotFoundException, get_bounty_from_invite_url, get_orgs_perms, profile_helper,
)
Expand Down Expand Up @@ -3589,6 +3590,7 @@ def hackathon(request, hackathon='', panel='prizes'):

try:
hackathon_event = HackathonEvent.objects.filter(slug__iexact=hackathon).prefetch_related('sponsor_profiles').latest('id')
increment_view_count.delay([hackathon_event.pk], hackathon_event.content_type, request.user.id, 'individual')
except HackathonEvent.DoesNotExist:
return redirect(reverse('get_hackathons'))

Expand Down Expand Up @@ -4193,6 +4195,11 @@ def get_hackathons(request):
'upcoming': HackathonEvent.objects.upcoming().filter(visible=True).order_by('start_date'),
'finished': HackathonEvent.objects.finished().filter(visible=True).order_by('-start_date'),
}

pks = HackathonEvent.objects.filter(visible=True).values_list('pk', flat=True)
if len(pks):
increment_view_count.delay(list(pks), 'hackathon event', request.user.id, 'index')

params = {
'active': 'hackathons',
'title': 'Hackathons',
Expand Down
3 changes: 1 addition & 2 deletions app/dataviz/d3_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,8 +636,7 @@ def viz_draggable(request, key='email_open'):
bfs = BountyFulfillment.objects.filter(accepted=True)
limit = 50
usernames = list(
bfs.exclude(profile__handle=''
).distinct('profile__handle').values_list('profile__handle', flat=True)
bfs.exclude(profile__handle='').distinct('profile__handle').values_list('profile__handle', flat=True)
)[0:limit]
if request.GET.get('data'):
output = []
Expand Down
29 changes: 29 additions & 0 deletions app/economy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import json

from django.contrib.contenttypes.models import ContentType
from django.contrib.humanize.templatetags.humanize import naturaltime
from django.contrib.postgres.fields import JSONField
from django.core.serializers.json import DjangoJSONEncoder
Expand All @@ -39,6 +40,7 @@
from django.utils.timezone import localtime

import pytz
from app.redis_service import RedisService


class EncodeAnything(DjangoJSONEncoder):
Expand Down Expand Up @@ -134,6 +136,33 @@ def created_human_time(self):
from django.contrib.humanize.templatetags.humanize import naturaltime
return naturaltime(self.created_on)

@property
def content_type(self):
ct = ContentType.objects.get_for_model(self)
return str(ct)

@property
def view_count_redis_key(self):
key = f"{self.content_type}_{self.pk}"
return key

def set_view_count(self, amount):
try:
redis = RedisService().redis
result = redis.set(self.view_count_redis_key, amount)
except KeyError:
return 0

@property
def get_view_count(self):
try:
redis = RedisService().redis
result = redis.get(self.view_count_redis_key)
if not result:
return 0
return int(result.decode('utf-8'))
except KeyError:
return 0


class ConversionRate(SuperModel):
Expand Down
7 changes: 5 additions & 2 deletions app/grants/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ class GrantAdmin(GeneralAdmin):
'logo_asset', 'created_on', 'modified_on', 'team_member_list',
'subscriptions_links', 'contributions_links', 'logo', 'logo_svg', 'image_css',
'link', 'clr_matching', 'clr_prediction_curve', 'hidden', 'grant_type', 'next_clr_calc_date', 'last_clr_calc_date',
'metadata', 'categories', 'twitter_handle_1', 'twitter_handle_2'
'metadata', 'categories', 'twitter_handle_1', 'twitter_handle_2', 'view_count'
]
readonly_fields = [
'logo_svg_asset', 'logo_asset',
'team_member_list',
'subscriptions_links', 'contributions_links', 'link',
'migrated_to'
'migrated_to', 'view_count'
]
list_display =['pk', 'title', 'active','grant_type', 'link', 'hidden', 'migrated_to']
raw_id_fields = ['admin_profile']
Expand All @@ -106,6 +106,9 @@ def logo_svg_asset(self, instance):
return mark_safe(f'<img src="{instance.svg.url}" width="300" height="300" />')
return mark_safe('N/A')

def view_count(self, instance):
return instance.get_view_count

def team_member_list(self, instance):
items = []
for team_member in instance.team_members.all():
Expand Down
9 changes: 8 additions & 1 deletion app/grants/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from cacheops import cached_view
from chartit import PivotChart, PivotDataPool
from dashboard.models import Activity, Profile, SearchHistory
from dashboard.tasks import increment_view_count
from dashboard.utils import get_web3, has_tx_mined
from economy.utils import convert_amount
from gas.utils import conf_time_spread, eth_usd_conv_rate, gas_advisories, recommend_min_gas_price_to_confirm_in_time
Expand Down Expand Up @@ -306,6 +307,12 @@ def grants(request):

now = datetime.datetime.now()

# record view
pks = list([grant.pk for grant in grants])
if len(pks):
increment_view_count.delay(pks, grants[0].content_type, request.user.id, 'index')


current_partners = partners.filter(end_date__gte=now).order_by('-amount')
past_partners = partners.filter(end_date__lt=now).order_by('-amount')
current_partners_fund = 0
Expand Down Expand Up @@ -445,6 +452,7 @@ def grant_details(request, grant_id, grant_slug):
grant = Grant.objects.prefetch_related('subscriptions','team_members').get(
pk=grant_id, slug=grant_slug
)
increment_view_count.delay([grant.pk], grant.content_type, request.user.id, 'individual')
subscriptions = grant.subscriptions.filter(active=True, error=False, is_postive_vote=True).order_by('-created_on')
cancelled_subscriptions = grant.subscriptions.filter(active=False, error=False, is_postive_vote=True).order_by('-created_on')

Expand Down Expand Up @@ -549,7 +557,6 @@ def grant_details(request, grant_id, grant_slug):
'target': f'/activity?what={what}',
'pinned': pinned,
'what': what,
'can_pin': can_pin(request, what),
'activity_count': activity_count,
'contributors': contributors,
'clr_active': clr_active,
Expand Down
5 changes: 4 additions & 1 deletion app/kudos/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,15 @@ class TokenAdmin(admin.ModelAdmin):
ordering = ['-id']
search_fields = ['name', 'description']
raw_id_fields = ['contract']
readonly_fields = ['link']
readonly_fields = ['link', 'view_count']

def link(self, instance):
html = f"<a href={instance.url}>{instance.url}</a>"
return mark_safe(html)

def view_count(self, instance):
return instance.get_view_count


class TransferAdmin(admin.ModelAdmin):
raw_id_fields = ['recipient_profile', 'sender_profile', 'kudos_token', 'kudos_token_cloned_from']
Expand Down
9 changes: 9 additions & 0 deletions app/kudos/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import boto3
from dashboard.models import Activity, Profile, SearchHistory
from dashboard.notifications import maybe_market_kudos_to_email, maybe_market_kudos_to_github
from dashboard.tasks import increment_view_count
from dashboard.utils import get_nonce, get_web3, is_valid_eth_address
from dashboard.views import record_user_action
from gas.utils import recommend_min_gas_price_to_confirm_in_time
Expand Down Expand Up @@ -141,6 +142,11 @@ def marketplace(request):
logger.debug(e)
pass

# increment view counts
pks = list(token_list.values_list('pk', flat=True))
if len(pks):
increment_view_count.delay(pks, token_list.first().content_type, request.user.id, 'index')

listings = token_list.order_by(order_by).cache()
context = {
'is_outside': True,
Expand Down Expand Up @@ -221,6 +227,9 @@ def details(request, kudos_id, name):
token_id=kudos.cloned_from_id,
contract__address=kudos.contract.address,
)
# increment view counts
increment_view_count.delay([token.pk], token.content_type, request.user.id, 'individual')

# The real num_cloned_in_wild is only stored in the Gen0 Kudos token
kudos.num_clones_in_wild = token.num_clones_in_wild
# Create a new attribute to reference number of gen0 clones allowed
Expand Down
1 change: 0 additions & 1 deletion app/townsquare/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ def get_sidebar_tabs(request):
'badge': get_amount_unread('everywhere', request),
}]
default_tab = 'everywhere'

if request.user.is_authenticated:
num_business_relationships = len(set(get_my_earnings_counter_profiles(request.user.profile.pk)))
if num_business_relationships:
Expand Down
Loading

0 comments on commit 5c21326

Please sign in to comment.