Skip to content

Commit 053b54d

Browse files
ericholscheragjohnson
authored andcommitted
Some ad changes around opting out. (#2951)
* Allow users to opt out of ads * Small update to verbage * Properly filter opted out users * Add boolean instead for community ads. * Add opted out projects as community only as well * Small wording change * Make user opt out follow same defaults/verbage as projects
1 parent 7d586be commit 053b54d

File tree

8 files changed

+96
-8
lines changed

8 files changed

+96
-8
lines changed

readthedocs/core/forms.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class UserProfileForm(forms.ModelForm):
2222
class Meta(object):
2323
model = UserProfile
2424
# Don't allow users edit someone else's user page,
25-
fields = ['first_name', 'last_name', 'homepage']
25+
fields = ['first_name', 'last_name', 'homepage', 'allow_ads']
2626

2727
def __init__(self, *args, **kwargs):
2828
super(UserProfileForm, self).__init__(*args, **kwargs)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.9.12 on 2017-06-14 18:06
3+
from __future__ import unicode_literals
4+
5+
import annoying.fields
6+
from django.conf import settings
7+
from django.db import migrations, models
8+
import django.db.models.deletion
9+
10+
11+
class Migration(migrations.Migration):
12+
13+
dependencies = [
14+
('core', '0003_add_banned_status'),
15+
]
16+
17+
operations = [
18+
migrations.AddField(
19+
model_name='userprofile',
20+
name='allow_ads',
21+
field=models.BooleanField(default=True, help_text='If unchecked, you will still see community ads.', verbose_name='See paid advertising'),
22+
),
23+
migrations.AlterField(
24+
model_name='userprofile',
25+
name='user',
26+
field=annoying.fields.AutoOneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL, verbose_name='User'),
27+
),
28+
]

readthedocs/core/models.py

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class UserProfile (models.Model):
2323
whitelisted = models.BooleanField(_('Whitelisted'), default=False)
2424
banned = models.BooleanField(_('Banned'), default=False)
2525
homepage = models.CharField(_('Homepage'), max_length=100, blank=True)
26+
allow_ads = models.BooleanField(_('See paid advertising'),
27+
help_text=_('If unchecked, you will still see community ads.'),
28+
default=True,
29+
)
2630
allow_email = models.BooleanField(_('Allow email'),
2731
help_text=_('Show your email on VCS '
2832
'contributions.'),
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-06-14 17: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', '0011_add-theme-filter'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='supporterpromo',
17+
name='community',
18+
field=models.BooleanField(default=False, verbose_name='Community Ad'),
19+
),
20+
]

readthedocs/donate/models.py

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SupporterPromo(models.Model):
7070
theme = models.CharField(_('Theme'), max_length=40,
7171
choices=THEMES, default=READTHEDOCS_THEME,
7272
blank=True, null=True)
73+
community = models.BooleanField(_('Community Ad'), default=False)
7374
live = models.BooleanField(_('Live'), default=False)
7475

7576
class Meta(object):

readthedocs/donate/signals.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ def choose_promo(promo_list):
9090
return None
9191

9292

93-
def get_promo(country_code, programming_language, theme, gold_project=False, gold_user=False):
93+
def get_promo(country_code, programming_language, theme,
94+
gold_project=False, gold_user=False, community_only=False):
9495
"""
9596
Get a proper promo.
9697
@@ -104,6 +105,9 @@ def get_promo(country_code, programming_language, theme, gold_project=False, gol
104105
"""
105106
promo_queryset = SupporterPromo.objects.filter(live=True, display_type='doc')
106107

108+
if community_only:
109+
promo_queryset = promo_queryset.filter(community=True)
110+
107111
filtered_promos = []
108112
for promo in promo_queryset:
109113
# Break out if we aren't meant to show to this language
@@ -157,6 +161,15 @@ def is_gold_project(project):
157161
return project.gold_owners.count()
158162

159163

164+
def is_community_only(user, project):
165+
"""Return True is this project or user should only be shown community ads"""
166+
if user.is_authenticated() and user.profile.as_opt_out:
167+
return True
168+
if not project.allow_promos:
169+
return True
170+
return False
171+
172+
160173
def get_user_country(request):
161174
"""Return the ISO country code from geo-IP data, or None if not found."""
162175
if not PROMO_GEO_PATH:
@@ -200,11 +213,9 @@ def lookup_promo(request, project, theme):
200213
if no promo should be shown.
201214
202215
"""
203-
if not project.allow_promos:
204-
return None
205-
206216
gold_user = is_gold_user(request.user)
207217
gold_project = is_gold_project(project)
218+
community_only = is_community_only(request.user, project)
208219

209220
# Don't show promos to gold users or on gold projects for now
210221
# (Some day we may show them something customised for them)
@@ -217,6 +228,7 @@ def lookup_promo(request, project, theme):
217228
theme=theme,
218229
gold_project=gold_project,
219230
gold_user=gold_user,
231+
community_only=community_only,
220232
)
221233

222234
# If we don't have anything to show, don't show it.

readthedocs/donate/tests.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
import mock
55

66
from builtins import range
7+
from django.contrib.auth.models import User
78
from django.test import TestCase
89
from django.core.urlresolvers import reverse
910
from django.core.cache import cache
1011
from django.test.client import RequestFactory
1112
from django_dynamic_fixture import get
1213

13-
from readthedocs.core.middleware import FooterNoSessionMiddleware
14+
from .core.middleware import FooterNoSessionMiddleware
1415
from .models import SupporterPromo, GeoFilter, Country
1516
from .constants import (CLICKS, VIEWS, OFFERS,
1617
INCLUDE, EXCLUDE)
@@ -184,6 +185,28 @@ def test_project_disabling(self):
184185
resp = json.loads(r.content)
185186
self.assertEqual(resp['promo'], False)
186187

188+
def test_user_disabling(self):
189+
"""Test that the promo doesn't show when the project has it disabled"""
190+
user = User.objects.get(username='test')
191+
user.profile.allow_ads = False
192+
user.profile.save()
193+
194+
# No ads for logged in user
195+
self.login('test', 'test')
196+
r = self.client.get(
197+
'/api/v2/footer_html/?project=pip&version=latest&page=index'
198+
)
199+
resp = json.loads(r.content)
200+
self.assertEqual(resp['promo'], False)
201+
202+
# Ads for logged out user
203+
self.logout()
204+
r = self.client.get(
205+
'/api/v2/footer_html/?project=pip&version=latest&page=index'
206+
)
207+
resp = json.loads(r.content)
208+
self.assertTrue(resp['promo'] is not False)
209+
187210

188211
class FilterTests(TestCase):
189212

readthedocs/projects/models.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ class Project(models.Model):
158158
build_queue = models.CharField(
159159
_('Alternate build queue id'), max_length=32, null=True, blank=True)
160160
allow_promos = models.BooleanField(
161-
_('Sponsor advertisements'), default=True, help_text=_(
162-
"Allow sponsor advertisements on my project documentation"))
161+
_('Allow paid advertising'), default=True, help_text=_(
162+
"If unchecked, users will still see community ads."))
163163

164164
# Sphinx specific build options.
165165
enable_epub_build = models.BooleanField(

0 commit comments

Comments
 (0)