Skip to content
This repository has been archived by the owner on Feb 13, 2019. It is now read-only.

Commit

Permalink
Merge branch 'master' into sc-landing-targeting
Browse files Browse the repository at this point in the history
  • Loading branch information
spra85 committed Jun 23, 2016
2 parents d9dcf12 + 6a60329 commit f9c15bc
Show file tree
Hide file tree
Showing 51 changed files with 1,237 additions and 124 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -22,5 +22,6 @@ venv/
.cache/
.sass-cache/
.map
scripts/.state
node_modules
*.sw[nop]
5 changes: 4 additions & 1 deletion .travis.yml
Expand Up @@ -17,6 +17,8 @@ cache:
- $HOME/.cache/pip
services:
- postgresql
addons:
postgresql: "9.4"
before_install:
# Use same ES version as in production (also the Travis "elasticsearch" service setting fails to start)
- travis_retry curl -OL https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-$ELASTICSEARCH_VERSION.deb
Expand All @@ -26,7 +28,8 @@ install:
- travis_retry pip install -e .
- travis_retry pip install "file://$(pwd)#egg=django-bulbs[dev]"
before_script:
- psql -c 'create database bulbs;' -U postgres
- psql -c "create database bulbs_test;" -U postgres
- psql -c "create user bulbs WITH superuser PASSWORD 'testing';" -U postgres
# Wait for ES startup
- until curl http://localhost:9200/; do sleep 1; done
script:
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,22 @@

## Development

## Version 2.9.2

- Added mobile ad placer static files to bulbs ads

## Version 2.8.4

- Added `PolymorphicContentMetadata` to register custom metadata responses with given serializers.
- Added `Infographic` models..
- Added `InfographicSerializer` and `InfographicMetadata` to API.
- Added Docker configurations for local testing.
- Added Postgresql9.4 testing for .travis.yml builds.

## Version 2.8.2

- Changed `post_to_instant_articles_api` to get the Article ID based on its canonical url

## Version 2.8.1

- Reverted `AppConfig` changes released in 2.8.0. Decided to go another route with duplicate app names.
Expand Down
25 changes: 25 additions & 0 deletions Dockerfile
@@ -0,0 +1,25 @@
FROM python:3.5
MAINTAINER Onion Tech <webtech@theonion.com>

RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get update \
&& apt-get install -y \
git-core \
libmemcached-dev \
libpq-dev \
postgresql-client-9.4 \
vim \
&& rm -rf /var/lib/apt/lists/*

# Setup app directory
WORKDIR /webapp

# Install as much as possible before adding app directory, to take advantage of
# Docker's layer caching.

# Container includes all requirements (so it works in dev + prod)
# Add app as late as possibly (will always trigger cache miss and rebuild from here)
ADD . /webapp
RUN pip install -e .
RUN pip install -e ".[dev]"
2 changes: 1 addition & 1 deletion bulbs/__init__.py
@@ -1 +1 @@
__version__ = "2.8.1"
__version__ = "2.9.2"
28 changes: 28 additions & 0 deletions bulbs/ads/static/js/mobile-ad-placer.js
@@ -0,0 +1,28 @@
'use strict';
var _ = require('lodash/lodash');
var adHtml = '<div class="dfp dfp-slot-inread" data-ad-unit="inread"></div>';


var MobileAdPlacer = {
placeAds: function () {
var body = $('body');
var paragraphs = body.find('p');
var wordCount = 0;
var adsPlaced = 0;

for (var i = 0; i < paragraphs.length; i++) {
var paragraphLength = _.words($(paragraphs[i]).html()).length;
wordCount = wordCount + paragraphLength;

if(wordCount > 350 && adsPlaced < 4) {
// place ad
$(paragraphs[i]).after(adHtml);
wordCount = 0;
adsPlaced++;
}
}
window.ads.loadAds();
},
};

module.exports = MobileAdPlacer;
56 changes: 56 additions & 0 deletions bulbs/ads/static/js/mobile-ad-placer.test.js
@@ -0,0 +1,56 @@
describe('MobileAdPlacer', function () {
var mobileAdPlacer = require('./mobile-ad-placer');
var faker = require('faker');

var article = '<section class="article-text"></section>'
var paragraph351 = '<p>' + faker.lorem.words(351) + '</p>';
var paragraph20 = '<p>' + faker.lorem.words(20) + '</p>';

beforeEach(function () {
$('body').append(article);
window.ads = {loadAds: function() { return }};
});

afterEach(function () {
$('.article-text').remove();
});

it('places ad after 350 words', function () {
$('.article-text').append(paragraph351);
mobileAdPlacer.placeAds();
var articleContents = $('.article-text').children();
expect($(articleContents[1]).attr('class')).to.equal("dfp dfp-slot-inread");
});

it('places multiple ads', function () {
$('.article-text').append(paragraph351);
$('.article-text').append(paragraph351);
adElement = document.createElement('ad');
mobileAdPlacer.placeAds(adElement);
var ads = $('.article-text').find('.dfp-slot-inread');
expect(ads.length).to.equal(2);
});

it('only places ads after paragraph breaks', function () {
adElement = document.createElement('ad');
var bigAssParagraph = '<p>' + faker.lorem.words(1000) + '</p>';
$('.article-text').append(bigAssParagraph);

mobileAdPlacer.placeAds(adElement);
var articleContents = $('.article-text').children();
// sanity check ad is placed
expect($('.article-text').find('.dfp').length).to.equal(1);
// not placed within p element
expect($('p').find('.dfp').length).to.equal(0);
});

it('places no more than 4 ads', function () {
adElement = document.createElement('ad');
for(var i = 0; i < 5; i++) {
$('.article-text').append(paragraph351);
}
mobileAdPlacer.placeAds();
var ads = $('.article-text').find('.dfp-slot-inread');
expect(ads.length).to.equal(4);
});
});
20 changes: 20 additions & 0 deletions bulbs/api/metadata.py
@@ -0,0 +1,20 @@
from rest_framework.metadata import SimpleMetadata

from bulbs.infographics.metadata import InfographicMetadata
from bulbs.infographics.serializers import InfographicSerializer


class PolymorphicContentMetadata(SimpleMetadata):

serializer_lookup = {
InfographicSerializer: InfographicMetadata()
}

def determine_metadata(self, request, view):
if hasattr(view, "get_serializer_class"):
serializer_class = view.get_serializer_class()
metadata = self.serializer_lookup.get(serializer_class, None)
if metadata:
return metadata.determine_metadata(request, view)
# TODO: Spike out why we included this generic (and bad) response.
return {"status": "ok"}
10 changes: 2 additions & 8 deletions bulbs/api/views.py
Expand Up @@ -48,17 +48,11 @@
from bulbs.special_coverage.serializers import SpecialCoverageSerializer
from bulbs.utils.methods import get_query_params, get_request_data

from .metadata import PolymorphicContentMetadata
from .mixins import UncachedResponse
from .permissions import CanEditContent, CanPublishContent


class ContentViewMetaData(BaseMetadata):
def determine_metadata(self, request, view):
return {
'status': 'ok',
}


class ContentViewSet(UncachedResponse, viewsets.ModelViewSet):
"""
uncached viewset for the `bulbs.content.Content` model
Expand All @@ -75,7 +69,7 @@ class ContentViewSet(UncachedResponse, viewsets.ModelViewSet):
"authors", "types"
)
permission_classes = [IsAdminUser, CanEditContent]
metadata_class = ContentViewMetaData
metadata_class = PolymorphicContentMetadata

def get_serializer_class(self):
"""gets the class type of the serializer
Expand Down
19 changes: 19 additions & 0 deletions bulbs/content/migrations/0012_auto_20160615_1605.py
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('content', '0011_auto_20160613_1116'),
]

operations = [
migrations.AlterField(
model_name='content',
name='template_choice',
field=models.IntegerField(default=0, choices=[(0, None), (1, 'special_coverage/landing.html')]),
),
]
9 changes: 4 additions & 5 deletions bulbs/content/models.py
Expand Up @@ -637,18 +637,17 @@ def delete_from_instant_article_api(sender, instance=None, **kwargs):
instance.id,
instance.instant_article_id,
delete.status_code,
delete.__dict__))
delete.__dict__
)
)
else:
status = delete.json().get('success')
if bool(status) is not True:
logger.error('''
Error in deleting Instant Article.\n
Content ID: {0}\n
IA ID: {1}\n
Error: {2}'''.format(
instance.id,
instance.instant_article_id,
delete.json()))
Error: {2}'''.format(instance.id, instance.instant_article_id, delete.json()))

##
# signal hooks
Expand Down
24 changes: 23 additions & 1 deletion bulbs/content/tasks.py
Expand Up @@ -125,9 +125,31 @@ def post_article(content, body, fb_page_id, fb_api_url, fb_token_path, fb_dev_mo

response = status.json().get('status')

# build URL
base = getattr(settings, 'WWW_URL')
if not base.startswith("http"):
base = "http://" + base

canonical_url = "{0}{1}".format(base, content.get_absolute_url())
canon = requests.get('{0}?id={1}&fields=instant_article&access_token={2}'.format(
fb_api_url,
canonical_url,
fb_access_token))

if not canon.ok:
logger.error('''
Error in getting article ID of Instant Article.\n
Content ID: {0}\n
Status Code: {1}
Request: {2}'''.format(
content.id,
canon.status_code,
canon.__dict__))
return

# set instant_article_id to response id
Content.objects.filter(pk=content.id).update(
instant_article_id=status.json().get('id'))
instant_article_id=canon.json().get('instant_article').get('id'))


def delete_article(content, fb_api_url, fb_token_path):
Expand Down
2 changes: 1 addition & 1 deletion bulbs/contributions/signals.py
Expand Up @@ -19,7 +19,7 @@ def update_feature_type_rates(sender, instance, created, *args, **kwargs):


@receiver(post_save, sender=ContributorRole)
def call_update_role_rates(sender, instance, * args, **kwargs):
def call_update_role_rates(sender, instance, *args, **kwargs):
update_role_rates.delay(instance.pk)


Expand Down
2 changes: 1 addition & 1 deletion bulbs/contributions/tasks.py
Expand Up @@ -16,7 +16,7 @@ def check_and_update_freelanceprofiles(content_id):

@shared_task(default_retry_delay=5)
def update_role_rates(contributor_role_pk):
for contribution in Contribution.objects.filter(contributor__pk=contributor_role_pk):
for contribution in Contribution.objects.filter(role__pk=contributor_role_pk):
contribution.index()


Expand Down
44 changes: 22 additions & 22 deletions bulbs/contributions/templates/reporting/__contribution_report.html
Expand Up @@ -64,60 +64,60 @@
</style>
</head>
<body>
<div style="color:#000;background: #f2f2f2;font-family: Helvetica, Arial, sans-serif;font-size: 16px;line-height: 1.3;margin: 0;min-width: 100% !important;padding: 1em 0;text-align: center;">
<div style="background: #fff;display: block;margin: 1em;max-width: 525px;text-align: left;width: 100%;">
<header>
<img src="https://s3.amazonaws.com/onionstatic/avclub/static/core/img/av-logo-compact_2x_invert.png" alt="The A.V. Club" />
<div style="color:#000;background-color:#f2f2f2;background-image:none;background-repeat:repeat;background-position:top left;background-attachment:scroll;font-family:Helvetica, Arial, sans-serif;font-size:16px;line-height:1.3;margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;min-width:100% !important;padding-top:1em;padding-bottom:1em;padding-right:0;padding-left:0;text-align:center;" >
<div style="background-color:#fff;background-image:none;background-repeat:repeat;background-position:top left;background-attachment:scroll;display:block;margin-top:1em;margin-bottom:1em;margin-right:1em;margin-left:1em;max-width:525px;text-align:left;width:100%;" >
<header style="background-color:#191f2c;background-image:none;background-repeat:repeat;background-position:top left;background-attachment:scroll;padding-top:1.3em;padding-bottom:1.3em;padding-right:1.3em;padding-left:1.3em;text-align:center;" >
<img src="http://v.theonion.com/onionstatic/avclub/static/core/img/av-logo-compact_2x_invert.png" alt="The A.V. Club" style="max-width:150px;width:100%;" />
</header>
{% autoescape on %}
<p style="color:inherit; margin:2em">
<p style="color:inherit;margin-top:2em;margin-bottom:2em;margin-right:2em;margin-left:2em;" >
Hey <b>{{ contributor.get_full_name }}</b>,
</p>
<p style="margin:2em">
<p style="margin-top:2em;margin-bottom:2em;margin-right:2em;margin-left:2em;" >
Please check the amount below and make sure it matches your records. The total is <b>${{ total|floatformat:2 }}</b>.
If you have any questions about it, please hit reply-all and let us know by <b>{{ deadline }}</b>
All payments will be processed by the 15th of the month.
</p>
<p style="background: #90a6ba; color: #fff; display: inline-block; padding: 0.75em 1em; margin: 0 2em;">
<p style="background-color:#90a6ba;background-image:none;background-repeat:repeat;background-position:top left;background-attachment:scroll;color:#fff;display:inline-block;padding-top:0.75em;padding-bottom:0.75em;padding-right:1em;padding-left:1em;margin-top:0;margin-bottom:0;margin-right:2em;margin-left:2em;" >
Total: ${{ total|floatformat:2 }}
</p>
<table style="margin: 2em">
<thead>
<table style="margin-top:2em;margin-bottom:2em;margin-right:2em;margin-left:2em;border-collapse:collapse;display:block;font-size:0.85em;" >
<thead style="font-size:0.9em;color:#90a6ba;letter-spacing:1px;text-transform:uppercase;" >
<tr>
<th>Amount</th>
<th>Date</th>
<th>Title / Feature Type</th>
<th style="padding-top:1.2em;padding-bottom:1.2em;padding-right:1.2em;padding-left:1.2em;" >Amount</th>
<th style="padding-top:1.2em;padding-bottom:1.2em;padding-right:1.2em;padding-left:1.2em;" >Date</th>
<th style="padding-top:1.2em;padding-bottom:1.2em;padding-right:1.2em;padding-left:1.2em;" >Title / Feature Type</th>
</tr>
</thead>
<tbody>
{% for contribution, content_type in contributions.items %}
<tr>
<td style="text-align: center; max-width: 100px;">
<tr style="border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#ddd;" >
<td style="text-align:center;max-width:100px;padding-top:1.2em;padding-bottom:1.2em;padding-right:1.2em;padding-left:1.2em;border-color:#ddd;border-style:solid;border-width:0 1px 0 0;font-weight:bold;" >
${{ contribution.pay|floatformat:2 }}
</td>
<td style="text-align: center; max-width: 100px;">
<td style="text-align:center;max-width:100px;padding-top:1.2em;padding-bottom:1.2em;padding-right:1.2em;padding-left:1.2em;border-color:#ddd;border-style:solid;border-width:0 1px 0 0;font-weight:bold;" >
{{ contribution.content.published|date:"m/d/y" }}
</td>
<td>
<a href="{{ contribution.content.get_absolute_url }}" style="color: #000;">
<td style="padding-top:1.2em;padding-bottom:1.2em;padding-right:1.2em;padding-left:1.2em;border-color:#ddd;border-style:solid;border-width:0 1px 0 0;font-weight:bold;" >
<a href="{{ contribution.content.get_absolute_url }}" style="color:#000;text-decoration:none;" >
{{ contribution.content.title|safe }}
</a>
<br>
<span>
<span style="color:#333;display:inline-block;font-size:0.8em;font-weight:normal;margin-top:8px;text-transform:uppercase;" >
{{ contribution.content.feature_type.name }}
</span>
</td>
</tr>
{% endfor %}
{% for line_item in line_items %}
<tr>
<td style="text-align: center; max-width: 100px;">
<tr style="border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#ddd;" >
<td style="text-align:center;max-width:100px;padding-top:1.2em;padding-bottom:1.2em;padding-right:1.2em;padding-left:1.2em;border-color:#ddd;border-style:solid;border-width:0 1px 0 0;font-weight:bold;" >
${{ line_item.amount|floatformat:2 }}
</td>
<td style="text-align: center; max-width: 100px;">
<td style="text-align:center;max-width:100px;padding-top:1.2em;padding-bottom:1.2em;padding-right:1.2em;padding-left:1.2em;border-color:#ddd;border-style:solid;border-width:0 1px 0 0;font-weight:bold;" >
{{ line_item.payment_date|date:"m/d/y" }}
</td>
<td>
<td style="padding-top:1.2em;padding-bottom:1.2em;padding-right:1.2em;padding-left:1.2em;border-color:#ddd;border-style:solid;border-width:0 1px 0 0;font-weight:bold;" >
{{ line_item.note }}
</td>
</tr>
Expand Down
Empty file added bulbs/infographics/__init__.py
Empty file.

0 comments on commit f9c15bc

Please sign in to comment.