Skip to content

Commit a0a29e8

Browse files
authored
Merge pull request #2897 from lordmauve/lint-fix-donate
Fix lint issues for donate app
2 parents 3531c85 + 6dd9e15 commit a0a29e8

File tree

11 files changed

+115
-63
lines changed

11 files changed

+115
-63
lines changed

prospector-more.yml

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ inherits: prospector
33
strictness: medium
44

55
ignore-paths:
6-
- donate/
76
- restapi/
87

98
pylint:

readthedocs/donate/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
"""Django app for one-time donations and promotions."""
2+
13
default_app_config = 'readthedocs.donate.apps.DonateAppConfig'

readthedocs/donate/admin.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Django admin configuration for the donate app."""
2+
13
from django.contrib import admin
24
from .models import (Supporter, SupporterPromo, Country,
35
PromoImpressions, GeoFilter)
@@ -8,10 +10,11 @@
810

911

1012
def set_default_countries(modeladmin, request, queryset):
13+
del modeladmin, request # unused arguments
1114
for project in queryset:
12-
filter = project.geo_filters.create(filter_type='exclude')
15+
geo_filter = project.geo_filters.create(filter_type='exclude')
1316
for country in Country.objects.filter(country__in=DEFAULT_EXCLUDES):
14-
filter.countries.add(country)
17+
geo_filter.countries.add(country)
1518
set_default_countries.short_description = "Add default exclude countries to this Promo"
1619

1720

readthedocs/donate/apps.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Django app config for the donate app."""
2+
13
from django.apps import AppConfig
24

35

readthedocs/donate/constants.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Constants used by the donate app."""
2+
13
DISPLAY_CHOICES = (
24
('doc', 'Documentation Pages'),
35
('site-footer', 'Site Footer'),

readthedocs/donate/forms.py

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import logging
44

55
from django import forms
6-
from django.conf import settings
76
from django.utils.translation import ugettext_lazy as _
87

98
from readthedocs.payments.forms import StripeModelForm, StripeResourceMixin

readthedocs/donate/models.py

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
"""Django models for the donate app."""
2+
# We use 'type' and 'hash' heavily in the API here.
3+
# pylint: disable=redefined-builtin
4+
15
from django.db import models
26
from django.utils.crypto import get_random_string
37
from django.utils.translation import ugettext_lazy as _
@@ -33,11 +37,14 @@ class Supporter(models.Model):
3337
stripe_id = models.CharField(max_length=255)
3438
subscribed = models.BooleanField(default=False)
3539

36-
def __str__(self):
40+
def __unicode__(self):
3741
return self.name
3842

3943

4044
class SupporterPromo(models.Model):
45+
46+
"""A banner advertisement."""
47+
4148
pub_date = models.DateTimeField(_('Publication date'), auto_now_add=True)
4249
modified_date = models.DateTimeField(_('Modified date'), auto_now=True)
4350

@@ -62,7 +69,7 @@ class SupporterPromo(models.Model):
6269
class Meta:
6370
ordering = ('analytics_id', '-live')
6471

65-
def __str__(self):
72+
def __unicode__(self):
6673
return self.name
6774

6875
def as_dict(self):
@@ -110,7 +117,9 @@ def incr(self, type, project=None):
110117
impression.save()
111118

112119
# TODO: Support redis, more info on this PR
113-
# github.com/rtfd/readthedocs.org/pull/2105/files/1b5f8568ae0a7760f7247149bcff481efc000f32#r58253051
120+
#
121+
# https://github.com/rtfd/readthedocs.org
122+
# /pull/2105/files/1b5f8568ae0a7760f7247149bcff481efc000f32#r58253051
114123

115124
def view_ratio(self, day=None):
116125
if not day:
@@ -201,6 +210,9 @@ class PromoImpressions(BaseImpression):
201210
promo = models.ForeignKey(SupporterPromo, related_name='impressions',
202211
blank=True, null=True)
203212

213+
def __unicode__(self):
214+
return u'%s on %s' % (self.promo, self.date)
215+
204216

205217
class ProjectImpressions(BaseImpression):
206218

@@ -218,6 +230,9 @@ class ProjectImpressions(BaseImpression):
218230
class Meta:
219231
unique_together = ('project', 'promo', 'date')
220232

233+
def __unicode__(self):
234+
return u'%s / %s on %s' % (self.promo, self.project, self.date)
235+
221236

222237
class Country(models.Model):
223238
country = CountryField(unique=True)

readthedocs/donate/signals.py

+75-53
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Django signal plumbing for the donate app."""
2+
13
import random
24
import logging
35

@@ -25,14 +27,14 @@
2527

2628
def show_to_geo(promo, country_code):
2729
# Remove promo's that exclude this country.
28-
for filter in promo.geo_filters.all():
29-
if filter.filter_type == INCLUDE:
30-
if country_code in filter.codes:
30+
for geo_filter in promo.geo_geo_filters.all():
31+
if geo_filter.geo_filter_type == INCLUDE:
32+
if country_code in geo_filter.codes:
3133
continue
3234
else:
3335
return False
34-
if filter.filter_type == EXCLUDE:
35-
if country_code in filter.codes:
36+
if geo_filter.geo_filter_type == EXCLUDE:
37+
if country_code in geo_filter.codes:
3638
return False
3739

3840
return True
@@ -141,70 +143,89 @@ def get_promo(country_code, programming_language, theme, gold_project=False, gol
141143
return promo_obj
142144

143145

146+
def is_gold_user(user):
147+
"""Return True if the user is a Gold supporter."""
148+
return user.is_authenticated() and (
149+
user.gold.count() or
150+
user.goldonce.count()
151+
)
152+
153+
154+
def is_gold_project(project):
155+
"""Return True if the project has been mapped by a Gold supporter."""
156+
return project.gold_owners.count()
157+
158+
159+
def get_user_country(request):
160+
"""Return the ISO country code from geo-IP data, or None if not found."""
161+
if not PROMO_GEO_PATH:
162+
return None
163+
ip = request.META.get('REMOTE_ADDR')
164+
if not ip:
165+
return None
166+
try:
167+
geo_response = geo_reader.city(ip)
168+
return geo_response.country.iso_code
169+
except (AddressNotFoundError, ValueError): # Invalid IP
170+
return None
171+
172+
144173
@receiver(footer_response)
145-
def attach_promo_data(sender, **kwargs):
146-
request = kwargs['request']
147-
context = kwargs['context']
148-
resp_data = kwargs['resp_data']
174+
def attach_promo_data(sender, request, context, resp_data, **__):
175+
"""Insert promotion data keys into the footer API response."""
176+
del sender # unused
149177

150178
project = context['project']
151179
theme = context['theme']
152180

153-
# Bail out early if promo's are disabled.
154-
use_promo = getattr(settings, 'USE_PROMOS', True)
155-
if not use_promo:
181+
if getattr(settings, 'USE_PROMOS', True):
182+
promo = lookup_promo(request, project, theme)
183+
else:
184+
promo = None
185+
186+
if promo:
187+
resp_data.update({
188+
'promo': True,
189+
'promo_data': promo,
190+
})
191+
else:
156192
resp_data['promo'] = False
157-
return
158193

159-
gold_user = gold_project = False
160-
promo_obj = country_code = None
161194

162-
show_promo = project.allow_promos
195+
def lookup_promo(request, project, theme):
196+
"""Look up a promo to show for the given project.
197+
198+
Return a dict of promo_data for inclusion in the footer response, or None
199+
if no promo should be shown.
163200
164-
# The request is by a GoldUser
165-
if request.user.is_authenticated():
166-
if request.user.gold.count() or request.user.goldonce.count():
167-
gold_user = True
201+
"""
202+
if not project.allow_promos:
203+
return None
168204

169-
# A GoldUser has mapped this project
170-
if project.gold_owners.count():
171-
gold_project = True
205+
gold_user = is_gold_user(request.user)
206+
gold_project = is_gold_project(project)
172207

173-
# Don't show gold users promos.
174-
# This will get overridden if we have specific promos for them below.
208+
# Don't show promos to gold users or on gold projects for now
209+
# (Some day we may show them something customised for them)
175210
if gold_user or gold_project:
176-
show_promo = False
177-
178-
if PROMO_GEO_PATH:
179-
# Get geo information from the IP, but don't record it anywhere
180-
ip = request.META.get('REMOTE_ADDR')
181-
if ip:
182-
try:
183-
geo_response = geo_reader.city(ip)
184-
country_code = geo_response.country.iso_code
185-
except (AddressNotFoundError, ValueError): # Invalid IP
186-
country_code = None
187-
188-
# Try to get a promo if we should be using one.
189-
if show_promo:
190-
promo_obj = get_promo(
191-
country_code=country_code,
192-
programming_language=project.programming_language,
193-
theme=theme,
194-
gold_project=gold_project,
195-
gold_user=gold_user,
196-
)
211+
return None
212+
213+
promo_obj = get_promo(
214+
country_code=get_user_country(request),
215+
programming_language=project.programming_language,
216+
theme=theme,
217+
gold_project=gold_project,
218+
gold_user=gold_user,
219+
)
197220

198221
# If we don't have anything to show, don't show it.
199222
if not promo_obj:
200-
show_promo = False
201-
202-
if show_promo:
203-
promo_dict = offer_promo(promo_obj=promo_obj, project=project)
204-
resp_data['promo_data'] = promo_dict
223+
return None
205224

206-
# Set promo object on return JSON
207-
resp_data['promo'] = show_promo
225+
return offer_promo(
226+
promo_obj=promo_obj,
227+
project=project
228+
)
208229

209230

210231
@receiver(footer_response)
@@ -217,6 +238,7 @@ def index_theme_data(sender, **kwargs):
217238
This will allow us to give people fair warning before we put ads on their docs.
218239
219240
"""
241+
del sender # unused
220242
context = kwargs['context']
221243

222244
project = context['project']

readthedocs/donate/urls.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from django.conf.urls import url, include
1+
"""Django URL patterns for the donate app."""
2+
3+
from django.conf.urls import url
24

35
from .views import DonateCreateView, DonateListView, DonateSuccessView
46
from .views import PayAdsView, PaySuccess, PromoDetailView

readthedocs/donate/utils.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Support functions for donations."""
2+
13
import pytz
24
import datetime
35

readthedocs/donate/views.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
"""Donation views"""
2+
# We use 'hash' heavily in the API here.
3+
# pylint: disable=redefined-builtin
24

35
import logging
46

@@ -109,6 +111,7 @@ def get_context_data(self, **kwargs):
109111

110112

111113
def click_proxy(request, promo_id, hash):
114+
"""Track a click on a promotion and redirect to the link."""
112115
promo = get_object_or_404(SupporterPromo, pk=promo_id)
113116
count = cache.get(promo.cache_key(type=CLICKS, hash=hash), None)
114117
if count is None:
@@ -136,6 +139,7 @@ def click_proxy(request, promo_id, hash):
136139

137140

138141
def view_proxy(request, promo_id, hash):
142+
"""Track a view of a promotion and redirect to the image."""
139143
promo = get_object_or_404(SupporterPromo, pk=promo_id)
140144
if not promo.image:
141145
raise Http404('No image defined for this promo.')
@@ -174,7 +178,7 @@ def _add_promo_data(display_type):
174178
return promo_dict
175179

176180

177-
def promo_500(request, template_name='donate/promo_500.html', **kwargs):
181+
def promo_500(request, template_name='donate/promo_500.html', **__):
178182
"""A simple 500 handler so we get media"""
179183
promo_dict = _add_promo_data(display_type='error')
180184
r = render_to_response(template_name,
@@ -186,7 +190,7 @@ def promo_500(request, template_name='donate/promo_500.html', **kwargs):
186190
return r
187191

188192

189-
def promo_404(request, template_name='donate/promo_404.html', **kwargs):
193+
def promo_404(request, template_name='donate/promo_404.html', **__):
190194
"""A simple 404 handler so we get media"""
191195
promo_dict = _add_promo_data(display_type='error')
192196
response = get_redirect_response(request, path=request.get_full_path())

0 commit comments

Comments
 (0)