From 0235cb38b004fef365095469fef91707b351f017 Mon Sep 17 00:00:00 2001 From: Samuel Clay Date: Thu, 5 Jan 2017 18:26:50 -0800 Subject: [PATCH] Adding popularity query email. --- apps/analyzer/forms.py | 32 ++++++++++++++ apps/analyzer/models.py | 46 ++++++++++++++++++++- apps/analyzer/tasks.py | 16 +++++++ apps/analyzer/urls.py | 1 + apps/analyzer/views.py | 35 +++++++++++++++- apps/rss_feeds/models.py | 37 ++++++++++------- media/css/payments.css | 8 +++- templates/analyzer/popularity_query.xhtml | 46 +++++++++++++++++++++ templates/mail/email_popularity_query.txt | 9 ++++ templates/mail/email_popularity_query.xhtml | 19 +++++++++ urls.py | 1 + 11 files changed, 232 insertions(+), 18 deletions(-) create mode 100644 apps/analyzer/forms.py create mode 100644 apps/analyzer/tasks.py create mode 100644 templates/analyzer/popularity_query.xhtml create mode 100644 templates/mail/email_popularity_query.txt create mode 100644 templates/mail/email_popularity_query.xhtml diff --git a/apps/analyzer/forms.py b/apps/analyzer/forms.py new file mode 100644 index 000000000..5377f3b0c --- /dev/null +++ b/apps/analyzer/forms.py @@ -0,0 +1,32 @@ +import re +import requests +from django import forms +from vendor.zebra.forms import StripePaymentForm +from django.utils.safestring import mark_safe +from django.contrib.auth import authenticate +from django.contrib.auth.models import User +from apps.profile.models import change_password, blank_authenticate, MGiftCode +from apps.social.models import MSocialProfile + +class PopularityQueryForm(forms.Form): + email = forms.CharField(widget=forms.TextInput(), + label="Your email address", + required=False) + query = forms.CharField(widget=forms.TextInput(), + label="Keywords", + required=False) + + def __init__(self, *args, **kwargs): + super(PopularityQueryForm, self).__init__(*args, **kwargs) + + def clean_email(self): + if not self.cleaned_data['email']: + raise forms.ValidationError('Please enter in an email address.') + + return self.cleaned_data['email'] + + def clean_query(self): + if not self.cleaned_data['query']: + raise forms.ValidationError('Please enter in a keyword search query.') + + return self.cleaned_data['query'] diff --git a/apps/analyzer/models.py b/apps/analyzer/models.py index c3ee27f5e..cbd063065 100644 --- a/apps/analyzer/models.py +++ b/apps/analyzer/models.py @@ -1,8 +1,14 @@ +import datetime import mongoengine as mongo from collections import defaultdict from django.db import models from django.contrib.auth.models import User +from django.template.loader import render_to_string +from django.core.mail import EmailMultiAlternatives +from django.conf import settings from apps.rss_feeds.models import Feed +from apps.analyzer.tasks import EmailPopularityQuery +from utils import log as logging class FeatureCategory(models.Model): user = models.ForeignKey(User) @@ -23,6 +29,44 @@ class Category(models.Model): def __unicode__(self): return '%s (%s)' % (self.category, self.count) + +class MPopularityQuery(mongo.Document): + email = mongo.StringField() + query = mongo.StringField() + is_emailed = mongo.BooleanField() + creation_date = mongo.DateTimeField(default=datetime.datetime.now) + + meta = { + 'collection': 'popularity_query', + 'allow_inheritance': False, + } + + def __unicode__(self): + return "%s - \"%s\"" % (self.email, self.query) + + def queue_email(self): + EmailPopularityQuery.delay(pk=self.pk) + + def send_email(self): + filename = Feed.xls_query_popularity(self.query, limit=10) + xlsx = open(filename, "r") + + params = { + 'query': self.query + } + text = render_to_string('mail/email_popularity_query.txt', params) + html = render_to_string('mail/email_popularity_query.xhtml', params) + subject = "Keyword popularity spreadsheet: \"%s\"" % self.query + msg = EmailMultiAlternatives(subject, text, + from_email='NewsBlur <%s>' % settings.HELLO_EMAIL, + to=['<%s>' % (self.email)]) + msg.attach_alternative(html, "text/html") + msg.attach(filename, xlsx.read(), 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + msg.send() + + logging.debug(" -> ~BB~FM~SBSent email for popularity query: %s" % self) + + class MClassifierTitle(mongo.Document): user_id = mongo.IntField() feed_id = mongo.IntField() @@ -241,4 +285,4 @@ def sort_classifiers_by_feed(user, feed_ids=None, classifier_titles=classifier_titles[feed_id], classifier_tags=classifier_tags[feed_id]) - return classifiers \ No newline at end of file + return classifiers diff --git a/apps/analyzer/tasks.py b/apps/analyzer/tasks.py new file mode 100644 index 000000000..79374f53a --- /dev/null +++ b/apps/analyzer/tasks.py @@ -0,0 +1,16 @@ +import datetime +from celery.task import Task +from utils import log as logging + +class EmailPopularityQuery(Task): + + def run(self, pk): + from apps.analyzer.models import MPopularityQuery + + query = MPopularityQuery.objects.get(pk=pk) + logging.user(self.user, "~BB~FCRunning popularity query: ~SB%s" % query) + + query.send_email() + query.is_emailed = True + query.save() + diff --git a/apps/analyzer/urls.py b/apps/analyzer/urls.py index 768afec14..fd21c7282 100644 --- a/apps/analyzer/urls.py +++ b/apps/analyzer/urls.py @@ -4,5 +4,6 @@ from apps.analyzer import views urlpatterns = patterns('', (r'^$', views.index), (r'^save/?', views.save_classifier), + (r'^popularity/?', views.popularity_query), (r'^(?P\d+)', views.get_classifiers_feed), ) diff --git a/apps/analyzer/views.py b/apps/analyzer/views.py index f2da5f9f3..44c6c5782 100644 --- a/apps/analyzer/views.py +++ b/apps/analyzer/views.py @@ -3,15 +3,19 @@ from utils import log as logging from django.shortcuts import get_object_or_404 from django.views.decorators.http import require_POST from django.conf import settings +from django.template import RequestContext +from django.shortcuts import render_to_response from mongoengine.queryset import NotUniqueError from apps.rss_feeds.models import Feed from apps.reader.models import UserSubscription from apps.analyzer.models import MClassifierTitle, MClassifierAuthor, MClassifierFeed, MClassifierTag -from apps.analyzer.models import get_classifiers_for_user +from apps.analyzer.models import get_classifiers_for_user, MPopularityQuery +from apps.analyzer.forms import PopularityQueryForm from apps.social.models import MSocialSubscription from utils import json_functions as json from utils.user_functions import get_user from utils.user_functions import ajax_login_required +from utils.view_functions import render_to def index(requst): pass @@ -116,4 +120,31 @@ def get_classifiers_feed(request, feed_id): response = dict(code=code, payload=payload) return response - \ No newline at end of file + +def popularity_query(request): + if request.method == 'POST': + form = PopularityQueryForm(request.POST) + if form.is_valid(): + logging.user(request.user, "~BC~FRPopularity query: ~SB%s~SN requests \"~SB~FM%s~SN~FR\"" % (request.POST['email'], request.POST['query'])) + query = MPopularityQuery.objects.create(email=request.POST['email'], + query=request.POST['query']) + query.queue_email() + + response = render_to_response('analyzer/popularity_query.xhtml', { + 'success': True, + 'popularity_query_form': form, + }, context_instance=RequestContext(request)) + response.set_cookie('newsblur_popularity_query', request.POST['query']) + + return response + else: + logging.user(request.user, "~BC~FRFailed popularity query: ~SB%s~SN requests \"~SB~FM%s~SN~FR\"" % (request.POST['email'], request.POST['query'])) + else: + logging.user(request.user, "~BC~FRPopularity query form loading") + form = PopularityQueryForm(initial={'query': request.COOKIES.get('newsblur_popularity_query', "")}) + + response = render_to_response('analyzer/popularity_query.xhtml', { + 'popularity_query_form': form, + }, context_instance=RequestContext(request)) + + return response diff --git a/apps/rss_feeds/models.py b/apps/rss_feeds/models.py index 3eacfc79a..e2a6206df 100644 --- a/apps/rss_feeds/models.py +++ b/apps/rss_feeds/models.py @@ -1590,33 +1590,37 @@ class Feed(models.Model): @classmethod def xls_query_popularity(cls, queries, limit): import xlsxwriter - workbook = xlsxwriter.Workbook('NewsBlurPopularity.xlsx') + from xlsxwriter.utility import xl_rowcol_to_cell + + if isinstance(queries, unicode): + queries = [q.strip() for q in queries.split(',')] + + title = 'NewsBlur-%s.xlsx' % slugify('-'.join(queries)) + workbook = xlsxwriter.Workbook(title) bold = workbook.add_format({'bold': 1}) date_format = workbook.add_format({'num_format': 'mmm d yyyy'}) unread_format = workbook.add_format({'font_color': '#E0E0E0'}) - if isinstance(queries, str): - queries = [q.strip() for q in queries.split(',')] for query in queries: worksheet = workbook.add_worksheet(query) row = 1 col = 0 - worksheet.write(0, col, 'Publisher', bold) + worksheet.write(0, col, 'Publisher', bold) worksheet.set_column(col, col, 15); col += 1 worksheet.write(0, col, 'Feed URL', bold) worksheet.set_column(col, col, 20); col += 1 worksheet.write(0, col, 'Reach score', bold) - worksheet.set_column(col, col, 8); col += 1 + worksheet.set_column(col, col, 9); col += 1 worksheet.write(0, col, '# subs', bold) - worksheet.set_column(col, col, 8); col += 1 + worksheet.set_column(col, col, 5); col += 1 worksheet.write(0, col, '# readers', bold) worksheet.set_column(col, col, 8); col += 1 - worksheet.write(0, col, 'Read %', bold) - worksheet.set_column(col, col, 8); col += 1 + worksheet.write(0, col, "read %", bold) + worksheet.set_column(col, col, 6); col += 1 worksheet.write(0, col, '# stories 30d', bold) worksheet.set_column(col, col, 10); col += 1 worksheet.write(0, col, '# shared', bold) - worksheet.set_column(col, col, 8); col += 1 + worksheet.set_column(col, col, 7); col += 1 worksheet.write(0, col, '# feed pos', bold) worksheet.set_column(col, col, 8); col += 1 worksheet.write(0, col, '# feed neg', bold) @@ -1624,9 +1628,9 @@ class Feed(models.Model): worksheet.write(0, col, 'Author', bold) worksheet.set_column(col, col, 15); col += 1 worksheet.write(0, col, '# author pos', bold) - worksheet.set_column(col, col, 8); col += 1 + worksheet.set_column(col, col, 10); col += 1 worksheet.write(0, col, '# author neg', bold) - worksheet.set_column(col, col, 8); col += 1 + worksheet.set_column(col, col, 10); col += 1 worksheet.write(0, col, 'Story title', bold) worksheet.set_column(col, col, 30); col += 1 worksheet.write(0, col, 'Story URL', bold) @@ -1638,16 +1642,20 @@ class Feed(models.Model): worksheet.write(0, col, 'Tag Count', bold) worksheet.set_column(col, col, 8); col += 1 worksheet.write(0, col, '# tag pos', bold) - worksheet.set_column(col, col, 8); col += 1 + worksheet.set_column(col, col, 7); col += 1 worksheet.write(0, col, '# tag neg', bold) - worksheet.set_column(col, col, 8); col += 1 + worksheet.set_column(col, col, 7); col += 1 popularity = cls.query_popularity(query, limit=limit) for feed in popularity: col = 0 worksheet.write(row, col, feed['feed_title']); col += 1 worksheet.write_url(row, col, feed['feed_url']); col += 1 - worksheet.write(row, col, feed['reach_score']); col += 1 + worksheet.write(row, col, "=%s*%s*%s" % ( + xl_rowcol_to_cell(row, col+2), + xl_rowcol_to_cell(row, col+3), + xl_rowcol_to_cell(row, col+4), + )); col += 1 worksheet.write(row, col, feed['num_subscribers']); col += 1 worksheet.write(row, col, feed['reader_count']); col += 1 worksheet.write(row, col, feed['read_pct']); col += 1 @@ -1688,6 +1696,7 @@ class Feed(models.Model): 'value': 0, 'format': unread_format}) workbook.close() + return title def find_stories(self, query, order="newest", offset=0, limit=25): story_ids = SearchStory.query(feed_ids=[self.pk], query=query, order=order, diff --git a/media/css/payments.css b/media/css/payments.css index 5580414ac..6c5fd3586 100644 --- a/media/css/payments.css +++ b/media/css/payments.css @@ -70,7 +70,13 @@ -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - +} +.NB-static-form .NB-label-right { + margin: 0 0 24px 206px; + width: 200px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } .NB-static-form input.error, diff --git a/templates/analyzer/popularity_query.xhtml b/templates/analyzer/popularity_query.xhtml new file mode 100644 index 000000000..de4a424b5 --- /dev/null +++ b/templates/analyzer/popularity_query.xhtml @@ -0,0 +1,46 @@ +{% extends 'base.html' %} + +{% load typogrify_tags utils_tags %} + +{% block bodyclass %}NB-static{% endblock %} +{% block extra_head_js %} + {% include_stylesheets "common" %} +{% endblock %} + +{% block content %} + +
+
+ +

NewsBlur Publisher Keyword Popularity XLSX Creator for YC Founders

+ +
Search for topics across millions of stories from millions of publishers
+ + {% if success %} +

Got it!
Email should be sent within the next minute.

+ {% else %} +
{% csrf_token %} +
+ {{ popularity_query_form.email.label_tag }} + {{ popularity_query_form.email }} + + {{ popularity_query_form.query.label_tag }} + {{ popularity_query_form.query }} +
Example: phillips hue, wemo, alexa
+
+ + {% if popularity_query_form.errors %} +
+ {% for field, error in popularity_query_form.errors.items %} + {{ error|safe }} + {% endfor %} +
+ {% endif %} + + +
+ {% endif %} +
+
+ +{% endblock %} \ No newline at end of file diff --git a/templates/mail/email_popularity_query.txt b/templates/mail/email_popularity_query.txt new file mode 100644 index 000000000..b8fe52855 --- /dev/null +++ b/templates/mail/email_popularity_query.txt @@ -0,0 +1,9 @@ +{% extends "mail/email_base.txt" %} + +{% load utils_tags %} + +{% block body %}Here's your keyword popularity spreadsheet + +This service is provided for free to YC companies. In the attached spreadsheet, you should note that individual fields have comments where appropriate. Feel free to reply directly if you have any questions. + +- Sam Clay{% endblock body %} \ No newline at end of file diff --git a/templates/mail/email_popularity_query.xhtml b/templates/mail/email_popularity_query.xhtml new file mode 100644 index 000000000..a8ee15ec5 --- /dev/null +++ b/templates/mail/email_popularity_query.xhtml @@ -0,0 +1,19 @@ +{% extends "mail/email_base.xhtml" %} + +{% load utils_tags %} + +{% block body %} + +

+ Here's your keyword popularity spreadsheet +

+ +

+ This service is provided for free to YC companies. In the attached spreadsheet, you should note that individual fields have comments where appropriate. Feel free to reply directly if you have any questions. +

+ +

+ - Sam Clay +

+ +{% endblock %} diff --git a/urls.py b/urls.py index cebe95e12..607801759 100644 --- a/urls.py +++ b/urls.py @@ -24,6 +24,7 @@ urlpatterns = patterns('', (r'^story/.*?', reader_views.index), (r'^feed/?', social_views.shared_stories_rss_feed_noid), (r'^rss_feeds/', include('apps.rss_feeds.urls')), + (r'^analyzer/', include('apps.analyzer.urls')), (r'^classifier/', include('apps.analyzer.urls')), (r'^profile/', include('apps.profile.urls')), (r'^folder_rss/', include('apps.profile.urls')),