Skip to content
This repository has been archived by the owner on Oct 30, 2018. It is now read-only.

Commit

Permalink
fixes bug 1141173 - using autocompeter.com
Browse files Browse the repository at this point in the history
  • Loading branch information
peterbe committed Mar 9, 2015
1 parent c854a55 commit a2fda31
Show file tree
Hide file tree
Showing 17 changed files with 594 additions and 11 deletions.
22 changes: 22 additions & 0 deletions airmozilla/main/context_processors.py
Expand Up @@ -220,3 +220,25 @@ def _(*args, **kwargs):
return args[0]

return {'_': _}


def autocompeter(request):
"""We need to tell the Autocompeter service which groups the current
user should be able to view."""
key = getattr(settings, 'AUTOCOMPETER_KEY', None)
if not key:
return {}

groups = []
if request.user and request.user.is_active:
groups.append(Event.PRIVACY_CONTRIBUTORS)
if not is_contributor(request.user):
groups.append(Event.PRIVACY_COMPANY)
url = getattr(settings, 'AUTOCOMPETER_URL', '')
domain = getattr(settings, 'AUTOCOMPETER_DOMAIN', '')
return {
'include_autocompeter': True,
'autocompeter_domain': domain,
'autocompeter_groups': ','.join(groups),
'autocompeter_url': url,
}
8 changes: 8 additions & 0 deletions airmozilla/main/static/main/autocompeter/autocompeter.min.css
@@ -0,0 +1,8 @@
._ac-wrap{position:relative;display:inline-block}
._ac-wrap ._ac-hint{position:absolute;top:0;left:0;border-color:transparent;box-shadow:none;opacity:1;color:#b4b4b4;background:#fff}
._ac-wrap ._ac-foreground{background-color:transparent;position:relative;vertical-align:top}
._ac-wrap ._ac-results{z-index:10;position:absolute;background-color:#fff;border:1px solid #ebebeb;width:400px;display:none;border-radius:3px;box-shadow:0 1px 5px rgba(0,0,0,.25)}
._ac-wrap ._ac-results p{padding:5px;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-bottom:1px solid #ebebeb;cursor:pointer;text-align:left}
._ac-wrap ._ac-results p a{display:block}
._ac-wrap ._ac-results p:last-child{border-bottom:none}
._ac-wrap ._ac-results p.selected{background-color:#f2f2f2}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions airmozilla/main/static/main/css/autocompeter.css
@@ -0,0 +1,33 @@
/* This file is all about overrides for the autocompeter.min.css
library. */

._ac-wrap ._ac-hint {
background: none;
color: #2D373B;
}
._ac-wrap ._ac-results {
width: 290px;
}
@media only screen and (max-device-width : 320px) {
._ac-wrap ._ac-results {
width: 230px;
}
}
._ac-results p {
background-color: #20272A;
color: #B4B4B4;
}
._ac-wrap ._ac-results {
border-color: #666;
}
._ac-wrap ._ac-results p {
border-bottom: 1px dotted #666;
font-size: .857em;
}
._ac-wrap ._ac-results p.selected {
background-color: #2D373B;
}

._ac-wrap input[type="text"]:focus {
background-color: transparent;
}
4 changes: 2 additions & 2 deletions airmozilla/main/static/main/css/main.css
Expand Up @@ -26,8 +26,8 @@ body #page { background-color: #20272a; background-image: url("/static/main/img/
/* @Header *********/
#masthead { background-image: none !important; }

#site-title, #site-title a:link, #site-title a:visited { color: #c5ccd2; }
#site-title a:hover, #site-title a:focus, #site-title a:active { color: #ffe400; }
#site-title h1, #site-title h1 a:link, #site-title h1 a:visited { color: #c5ccd2; }
/*#site-title a:hover, #site-title a:focus, #site-title a:active { color: #ffe400; }*/

#site-title h1 { float: left; }
#site-title form {
Expand Down
10 changes: 5 additions & 5 deletions airmozilla/main/static/main/css/onemozilla.css
Expand Up @@ -90,8 +90,8 @@ h5 { font-size: 1.125em; }
h6 { font-size: 1em; }

/* @Links */
a:link, a:visited { color: #af3232; text-decoration: none; }
a:hover, a:focus, a:active { color: #962c2c; text-decoration: underline; }
a:link, a:visited { text-decoration: none; }
a:hover, a:focus, a:active { text-decoration: underline; }

/* @Accessible @Skip links *********/
#nav-access { position: absolute; top: -50em; }
Expand All @@ -116,8 +116,8 @@ px 68 160 252 344 436 528 620 712 804 896 */
#masthead { position: relative; min-height: 120px; margin: 0; background-position: right top; background-repeat: no-repeat; }
#masthead hgroup { padding-right: 340px; }
#site-title h1 { font-size: 4.5em; letter-spacing: -3px; margin: 0 0 .15em -5px; -moz-transition: font-size, letter-spacing .5s linear; -webkit-transition: font-size, letter-spacing .5s linear; -o-transition: font-size, letter-spacing .5s linear; -ms-transition: font-size, letter-spacing .5s linear; transition: font-size, letter-spacing .5s linear; }
#site-title a { color: #333; text-decoration: none; }
#site-title a:hover, #site-title a:focus, #site-title a:active { color: #962c2c; text-decoration: none; }
#site-title h1 a { color: #333; text-decoration: none; }
#site-title h1 a:hover, #site-title h1 a:focus, #site-title h1 a:active { text-decoration: none; }
#masthead #tabzilla { position: absolute; top: 0; right: 0; border: 0; }

#content { clear: both; }
Expand Down Expand Up @@ -343,7 +343,7 @@ input[type=email], input[type=url], input[type=search], input[type=password], in
font-size: .857em;
border: 1px solid #dbdbdb;
background: transparent;
background: rgba(0,0,0,0.05);
background: rgba(0,0,0,0.05);
box-shadow: inset rgba(0,0,0,0.1) 0 0 4px, rgba(255,255,255, 0.1) 0 1px 0;
padding: 6px 10px;
-moz-transition: all .1s linear;
Expand Down
16 changes: 16 additions & 0 deletions airmozilla/main/static/main/js/autocompeter.js
@@ -0,0 +1,16 @@
$(function() {
var config = $('autocompeter');
if (config.length) {
var options = {};
if (config.data('url')) {
options.url = config.data('url');
}
if (config.data('groups')) {
options.groups = config.data('groups');
}
if (config.data('domain')) {
options.domain = config.data('domain');
}
Autocompeter(document.getElementById('id_q'), options);
}
});
14 changes: 14 additions & 0 deletions airmozilla/main/templates/main/main_base.html
Expand Up @@ -24,6 +24,8 @@
{% compress css %}
<link href="{{ static('main/css/onemozilla.css') }}" rel="stylesheet" type="text/css">
<link href="{{ static('main/css/main.css') }}" rel="stylesheet" type="text/css">
<link href="{{ static('main/autocompeter/autocompeter.min.css') }}" rel="stylesheet" type="text/css">
<link href="{{ static('main/css/autocompeter.css') }}" rel="stylesheet" type="text/css">
<link href="{{ static('browserid/persona-buttons.css') }}" rel="stylesheet" type="text/css">
{% endcompress %}

Expand Down Expand Up @@ -143,6 +145,18 @@
{# the day Air Mozilla uses L10n we can remove this /en-US/ here. For now it reduces redirects. #}
{% if not DEV %}<script src="//mozorg.cdn.mozilla.net/en-US/tabzilla/tabzilla.js"></script>{% endif %}

{% if include_autocompeter %}
<autocompeter
data-groups="{{ autocompeter_groups }}"
data-domain="{{ autocompeter_domain }}"
data-url="{{ autocompeter_url }}"
></autocompeter>
{% endif %}

{% compress js %}
<script src="{{ static('main/autocompeter/autocompeter.min.js') }}"></script>
<script src="{{ static('main/js/autocompeter.js') }}"></script>
{% endcompress %}
{# Instead of using browser_js() we here include them manually. It'll make the page faster #}
{% compress js %}
<script src="{{ static('js/libs/include.js') }}"></script>
Expand Down
73 changes: 72 additions & 1 deletion airmozilla/main/tests/test_context_processors.py
@@ -1,11 +1,17 @@
from nose.tools import eq_

from django.contrib.auth.models import User, AnonymousUser
from django.test import TestCase
from django.test.client import RequestFactory
from django.conf import settings

from funfactory.urlresolvers import reverse

from airmozilla.main.context_processors import browserid
from airmozilla.main.models import Event, UserProfile
from airmozilla.main.context_processors import (
browserid,
autocompeter,
)


class TestBrowserID(TestCase):
Expand Down Expand Up @@ -33,3 +39,68 @@ def test_redirect_invalid_next(self):
request = RequestFactory().get('/some/page/?next=%s' % next)
result = browserid(request)['redirect_next']()
eq_(result, '/')


class TestAutocompeter(TestCase):

def setUp(self):
super(TestAutocompeter, self).setUp()
settings.AUTOCOMPETER_KEY = 'somethingrandomlooking'

def test_autocompeter_disabled(self):
request = RequestFactory().get('/')
request.user = AnonymousUser()
with self.settings(AUTOCOMPETER_KEY=None):
result = autocompeter(request)
eq_(result, {})

def test_autocompeter_anonymous(self):
request = RequestFactory().get('/')
request.user = AnonymousUser()
result = autocompeter(request)
eq_(result['autocompeter_groups'], '')

def test_autocompeter_employee(self):
request = RequestFactory().get('/')
request.user = User.objects.create(
username='employee'
)
result = autocompeter(request)
eq_(
result['autocompeter_groups'],
'%s,%s' % (Event.PRIVACY_CONTRIBUTORS, Event.PRIVACY_COMPANY)
)

def test_autocompeter_contributor(self):
request = RequestFactory().get('/')
request.user = User.objects.create(
username='contributor'
)
UserProfile.objects.create(
user=request.user,
contributor=True,
)
result = autocompeter(request)
eq_(
result['autocompeter_groups'],
'%s' % (Event.PRIVACY_CONTRIBUTORS,)
)

def test_autocompeter_different_domain(self):
request = RequestFactory().get('/')
request.user = AnonymousUser()
result = autocompeter(request)
eq_(result['autocompeter_domain'], '')
with self.settings(AUTOCOMPETER_DOMAIN='airmo'):
result = autocompeter(request)
eq_(result['autocompeter_domain'], 'airmo')

def test_autocompeter_different_url(self):
request = RequestFactory().get('/')
request.user = AnonymousUser()
result = autocompeter(request)
# this has a default in tests
eq_(result['autocompeter_url'], settings.AUTOCOMPETER_URL)
with self.settings(AUTOCOMPETER_URL='http://autocompeter.dev/v1'):
result = autocompeter(request)
eq_(result['autocompeter_url'], 'http://autocompeter.dev/v1')
122 changes: 122 additions & 0 deletions airmozilla/manage/autocompeter.py
@@ -0,0 +1,122 @@
import datetime
import json
import time

import requests

from django.conf import settings
from django.utils import timezone

from funfactory.urlresolvers import reverse

from airmozilla.main.models import Event, EventHitStats


def update(
verbose=False, all=False, flush_first=False, max_=1000,
since=datetime.timedelta(minutes=60)
):
if not getattr(settings, 'AUTOCOMPETER_KEY', None):
if verbose: # pragma: no cover
print "Unable to submit titles to autocompeter.com"
print "No settings.AUTOCOMPETER_KEY set up"
return

autocompeter_url = getattr(
settings,
'AUTOCOMPETER_URL',
'https://autocompeter.com/v1'
)
if flush_first:
assert all, "must be all if you're flushing"
t0 = time.time()
response = requests.delete(
autocompeter_url + '/flush',
headers={
'Auth-Key': settings.AUTOCOMPETER_KEY,
},
verify=not settings.DEBUG
)
t1 = time.time()
if verbose: # pragma: no cover
print response
print "Took", t1 - t0, "seconds to flush"
assert response.status_code == 204, response.status_code

now = timezone.now()

if all:
hits_map = dict(
EventHitStats.objects.all().values_list('event', 'total_hits')
)
values = hits_map.values()
if values:
median_hits = sorted(values)[len(values) / 2]
else:
median_hits = 0
events = Event.objects.approved()
else:
events = (
Event.objects.approved()
.filter(modified__gte=now-since)[:max_]
)
if events:
# there are events, we'll need a hits_map and a median
hits_map = dict(
EventHitStats.objects.filter(event__in=events)
.values_list('event', 'total_hits')
)
values = (
EventHitStats.objects.all()
.values_list('total_hits', flat=True)
)
if values:
median_hits = sorted(values)[len(values) / 2]
else:
median_hits = 0

documents = []
for event in events:
url = reverse('main:event', args=(event.slug,))
title = event.title
if event.start_time > now:
# future events can be important too
popularity = median_hits
else:
hits = hits_map.get(event.id, 0)
popularity = hits
if event.privacy == Event.PRIVACY_PUBLIC:
group = ''
else:
group = event.privacy

documents.append({
'title': title,
'url': url,
'popularity': popularity,
'group': group,
})

if verbose: # pragma: no cover
from pprint import pprint
pprint(documents)

if not documents:
if verbose: # pragma: no cover
print "No documents."
return

t0 = time.time()
response = requests.post(
autocompeter_url + '/bulk',
data=json.dumps({'documents': documents}),
headers={
'Auth-Key': settings.AUTOCOMPETER_KEY,
},
verify=not settings.DEBUG
)
t1 = time.time()
assert response.status_code == 201, response.status_code
if verbose: # pragma: no cover
print response
print "Took", t1 - t0, "seconds to bulk submit"

0 comments on commit a2fda31

Please sign in to comment.