Skip to content

Commit 817fe49

Browse files
authored
Merge pull request #2770 from rtfd/promo-filter-updates
Add a bit more color to the promo display.
2 parents 4c9585f + 40a6be6 commit 817fe49

File tree

10 files changed

+202
-28
lines changed

10 files changed

+202
-28
lines changed

readthedocs/donate/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class SupporterPromoAdmin(admin.ModelAdmin):
4545
model = SupporterPromo
4646
save_as = True
4747
prepopulated_fields = {'analytics_id': ('name',)}
48-
list_display = ('name', 'live', 'click_ratio', 'sold_impressions',
48+
list_display = ('name', 'live', 'total_click_ratio', 'click_ratio', 'sold_impressions',
4949
'total_views', 'total_clicks')
5050
list_filter = ('live', 'display_type')
5151
list_editable = ('live', 'sold_impressions')

readthedocs/donate/constants.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,13 @@
2222
VIEWS,
2323
CLICKS
2424
)
25+
26+
ANY = 'any'
27+
READTHEDOCS_THEME = 'sphinx_rtd_theme'
28+
ALABASTER_THEME = 'alabaster'
29+
30+
THEMES = (
31+
(ANY, 'Any'),
32+
(ALABASTER_THEME, 'Alabaster Theme'),
33+
(READTHEDOCS_THEME, 'Read the Docs Sphinx Theme'),
34+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.9.12 on 2017-04-04 13:48
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('donate', '0009_add-error-to-promos'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='supporterpromo',
17+
name='sold_clicks',
18+
field=models.IntegerField(default=0, verbose_name='Sold Clicks'),
19+
),
20+
migrations.AlterField(
21+
model_name='supporterpromo',
22+
name='sold_impressions',
23+
field=models.IntegerField(default=1000000, verbose_name='Sold Impressions'),
24+
),
25+
]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.9.12 on 2017-04-12 13:05
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('donate', '0010_add-sold-clicks'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='supporterpromo',
17+
name='theme',
18+
field=models.CharField(blank=True, choices=[(b'any', b'Any'), (b'alabaster', b'Alabaster Theme'), (b'sphinx_rtd_theme', b'Read the Docs Sphinx Theme')], default=b'sphinx_rtd_theme', max_length=40, null=True, verbose_name='Theme'),
19+
),
20+
]

readthedocs/donate/models.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from readthedocs.donate.utils import get_ad_day
1010
from readthedocs.donate.constants import (
11-
DISPLAY_CHOICES, FILTER_CHOICES, IMPRESSION_TYPES
11+
DISPLAY_CHOICES, FILTER_CHOICES, IMPRESSION_TYPES, THEMES, READTHEDOCS_THEME
1212
)
1313
from readthedocs.projects.models import Project
1414
from readthedocs.projects.constants import PROGRAMMING_LANGUAGES
@@ -48,11 +48,15 @@ class SupporterPromo(models.Model):
4848
image = models.URLField(_('Image URL'), max_length=255, blank=True, null=True)
4949
display_type = models.CharField(_('Display Type'), max_length=200,
5050
choices=DISPLAY_CHOICES, default='doc')
51-
sold_impressions = models.IntegerField(_('Sold Impressions'), default=1000)
51+
sold_impressions = models.IntegerField(_('Sold Impressions'), default=1000000)
5252
sold_days = models.IntegerField(_('Sold Days'), default=30)
53+
sold_clicks = models.IntegerField(_('Sold Clicks'), default=0)
5354
programming_language = models.CharField(_('Programming Language'), max_length=20,
5455
choices=PROGRAMMING_LANGUAGES, default=None,
5556
blank=True, null=True)
57+
theme = models.CharField(_('Theme'), max_length=40,
58+
choices=THEMES, default=READTHEDOCS_THEME,
59+
blank=True, null=True)
5660
live = models.BooleanField(_('Live'), default=False)
5761

5862
class Meta:
@@ -148,6 +152,15 @@ def total_click_ratio(self):
148152
(float(self.total_clicks()) / float(self.total_views())) * 100
149153
)
150154

155+
def report_html_text(self):
156+
"""
157+
Include the link in the html text.
158+
159+
Only used for reporting,
160+
doesn't include any click fruad protection!
161+
"""
162+
return self.text.replace('<a>', "<a href='%s'>" % self.link)
163+
151164

152165
class BaseImpression(models.Model):
153166
date = models.DateField(_('Date'))
@@ -172,24 +185,28 @@ def view_ratio(self):
172185
def click_ratio(self):
173186
if self.views == 0:
174187
return 0 # Don't divide by 0
175-
return float(
188+
return '%.3f' % float(
176189
float(self.clicks) / float(self.views) * 100
177190
)
178191

179192

180193
class PromoImpressions(BaseImpression):
181-
"""Track stats around how successful this promo has been.
194+
"""
195+
Track stats around how successful this promo has been.
182196
183-
Indexed one per promo per day."""
197+
Indexed one per promo per day.
198+
"""
184199

185200
promo = models.ForeignKey(SupporterPromo, related_name='impressions',
186201
blank=True, null=True)
187202

188203

189204
class ProjectImpressions(BaseImpression):
190-
"""Track stats for a specific project and promo.
205+
"""
206+
Track stats for a specific project and promo.
191207
192-
Indexed one per project per promo per day"""
208+
Indexed one per project per promo per day
209+
"""
193210

194211
promo = models.ForeignKey(SupporterPromo, related_name='project_impressions',
195212
blank=True, null=True)

readthedocs/donate/signals.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import random
2+
import logging
23

34
from django.dispatch import receiver
45
from django.conf import settings
6+
from django.core.cache import cache
7+
8+
import redis
59

610
from readthedocs.restapi.signals import footer_response
711
from readthedocs.donate.models import SupporterPromo
812
from readthedocs.donate.constants import INCLUDE, EXCLUDE
913
from readthedocs.donate.utils import offer_promo
1014

1115

16+
log = logging.getLogger(__name__)
17+
1218
PROMO_GEO_PATH = getattr(settings, 'PROMO_GEO_PATH', None)
1319

1420
if PROMO_GEO_PATH:
@@ -82,7 +88,7 @@ def choose_promo(promo_list):
8288
return None
8389

8490

85-
def get_promo(country_code, programming_language, gold_project=False, gold_user=False):
91+
def get_promo(country_code, programming_language, theme, gold_project=False, gold_user=False):
8692
"""
8793
Get a proper promo.
8894
@@ -97,15 +103,18 @@ def get_promo(country_code, programming_language, gold_project=False, gold_user=
97103
promo_queryset = SupporterPromo.objects.filter(live=True, display_type='doc')
98104

99105
filtered_promos = []
100-
for obj in promo_queryset:
106+
for promo in promo_queryset:
101107
# Break out if we aren't meant to show to this language
102-
if obj.programming_language and not show_to_programming_language(obj, programming_language):
108+
if promo.programming_language and not show_to_programming_language(promo, programming_language): # noqa
103109
continue
104110
# Break out if we aren't meant to show to this country
105-
if country_code and not show_to_geo(obj, country_code):
111+
if country_code and not show_to_geo(promo, country_code):
112+
continue
113+
# Don't show if the theme doesn't match
114+
if promo.theme not in ['any', theme]:
106115
continue
107116
# If we haven't bailed because of language or country, possibly show the promo
108-
filtered_promos.append(obj)
117+
filtered_promos.append(promo)
109118

110119
promo_obj = choose_promo(filtered_promos)
111120

@@ -140,6 +149,7 @@ def attach_promo_data(sender, **kwargs):
140149
resp_data = kwargs['resp_data']
141150

142151
project = context['project']
152+
theme = context['theme']
143153

144154
# Bail out early if promo's are disabled.
145155
use_promo = getattr(settings, 'USE_PROMOS', True)
@@ -181,6 +191,7 @@ def attach_promo_data(sender, **kwargs):
181191
promo_obj = get_promo(
182192
country_code=country_code,
183193
programming_language=project.programming_language,
194+
theme=theme,
184195
gold_project=gold_project,
185196
gold_user=gold_user,
186197
)
@@ -195,3 +206,25 @@ def attach_promo_data(sender, **kwargs):
195206

196207
# Set promo object on return JSON
197208
resp_data['promo'] = show_promo
209+
210+
211+
@receiver(footer_response)
212+
def index_theme_data(sender, **kwargs):
213+
"""
214+
Keep track of which projects are using which theme.
215+
216+
This is primarily used so we can send email to folks using alabaster,
217+
and other themes we might want to display ads on.
218+
This will allow us to give people fair warning before we put ads on their docs.
219+
220+
"""
221+
context = kwargs['context']
222+
223+
project = context['project']
224+
theme = context['theme']
225+
226+
try:
227+
redis_client = cache.get_client(None)
228+
redis_client.sadd("readthedocs:v1:index:themes:%s" % theme, project)
229+
except (AttributeError, redis.exceptions.ConnectionError):
230+
log.warning('Redis theme indexing error: %s', exc_info=True)

readthedocs/donate/static/donate/css/donate.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,13 @@
33
margin-right: auto;
44
width: 900px;
55
text-align: center
6-
}
6+
7+
.promo {
8+
margin-top: 1em;
9+
margin-bottom: 1em;
10+
width: 240px;
11+
}
12+
13+
.filters dt {
14+
font-weight: bold;
15+
}

readthedocs/donate/templates/donate/promo_detail.html

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,76 @@
55

66
{% block title %}{% trans "Promo Detail" %}{% endblock %}
77

8+
{% block extra_links %}
9+
<link rel="stylesheet" href="{% static 'donate/css/donate.css' %}" />
10+
{% endblock %}
11+
812
{% block content %}
913

1014
<h1> Promo Results </h1>
1115

1216
{% if promos %}
1317

18+
{% if promos|length > 1 %}
1419
<p>
15-
Total Clicks for all shown promos: {{ total_clicks }}
20+
<strong>Total Clicks for all shown promos</strong>: {{ total_clicks }}
1621
</p>
22+
{% endif %}
1723

24+
<div id="promo_detail">
1825
{% for promo in promos %}
1926

2027
<h3>
2128
Results for {{ promo.name }} ({{ promo.analytics_id }}) over last {{ days }} days.
2229
</h3>
2330

24-
<div class="example" style="width: 30%;">
25-
<a href="{{ promo.link }}"><img width=120 height=90 src="{{ promo.image }}"></a>
26-
<br>
27-
{{ promo.text|safe }}
31+
<div class="example">
32+
33+
<div class="filters">
34+
{% if promo.programming_language %}
35+
<dl>
36+
<dt>Filtered Language</dt>
37+
<dd>{{ promo.programming_language }}</dd>
38+
</dl>
39+
{% endif %}
40+
41+
{% if promo.geo_filters.count %}
42+
<dl>
43+
<dt>Filtered Geos</dt>
44+
{% for geo in promo.geo_filters.all %}
45+
<dd>
46+
{{ geo.get_filter_type_display }}: {{ geo.countries.all|join:", " }}
47+
</dd>
48+
{% endfor %}
49+
</dl>
50+
{% endif %}
51+
52+
{% if promo.sold_clicks %}
53+
<dl>
54+
<dt>Total Clicks Sold</dt>
55+
<dd>
56+
{{ promo.sold_clicks }}
57+
</dd>
58+
</dl>
59+
{% endif %}
60+
</div>
61+
62+
63+
<div class="promo">
64+
<div id="promo_image">
65+
<a href="{{ promo.link }}">
66+
<img width=120 height=90 src="{{ promo.image }}">
67+
</a>
68+
</div>
69+
70+
<div id="promo_text">
71+
{{ promo.report_html_text|safe }}
72+
</div>
73+
</div>
2874

2975
</div>
3076

31-
<br>
32-
77+
<h5>Promo Data</h5>
3378
<table>
3479
<tr>
3580
<th><strong>Day (UTC)</strong></th>
@@ -56,6 +101,7 @@ <h3>
56101
</table>
57102

58103
{% endfor %}
104+
</div>
59105

60106
{% else %}
61107

0 commit comments

Comments
 (0)