Skip to content

Commit

Permalink
Merge branch 'master' into design-update
Browse files Browse the repository at this point in the history
  • Loading branch information
Knut Hühne committed Jun 28, 2016
2 parents 627bae5 + 54c2ff8 commit 6e4257d
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 5 deletions.
33 changes: 33 additions & 0 deletions foundation/organisation/migrations/0003_add_nowdoing_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('organisation', '0002_sidebarextension'),
]

operations = [
migrations.CreateModel(
name='NowDoing',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('doing_type', models.CharField(max_length=10, choices=[(b'reading', b'reading'), (b'listening', b'listening'), (b'working', b'working'), (b'location', b'location'), (b'watching', b'watching'), (b'eating', b'eating')])),
('link', models.URLField(null=True, blank=True)),
('text', models.TextField(null=True, blank=True)),
],
),
migrations.AddField(
model_name='person',
name='username_on_slack',
field=models.CharField(max_length=100, null=True, blank=True),
),
migrations.AddField(
model_name='nowdoing',
name='person',
field=models.ForeignKey(to='organisation.Person'),
),
]
25 changes: 24 additions & 1 deletion foundation/organisation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Person(models.Model):
updated_at = models.DateTimeField(auto_now=True)

name = models.CharField(max_length=100)
username_on_slack = models.CharField(max_length=100, blank=True, null=True)
description = models.TextField(blank=True, null=True)
email = models.EmailField(blank=True)
photo = models.ImageField(upload_to='organisation/people/photos',
Expand All @@ -35,6 +36,27 @@ class Meta:
verbose_name_plural = "people"


class NowDoing(models.Model):
ACTIVITIES = (
('reading', 'reading'),
('listening', 'listening'),
('working', 'working'),
('location', 'location'),
('watching', 'watching'),
('eating', 'eating'),
)
person = models.ForeignKey(Person)
doing_type = models.CharField(
max_length=10,
choices=ACTIVITIES)
link = models.URLField(blank=True, null=True)
text = models.TextField(blank=True, null=True)

def __repr__(self):
return '<NowDoing: {}, {}>'.format(self.person.name,
self.doing_type)


class Unit(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Expand Down Expand Up @@ -286,7 +308,8 @@ class NetworkGroupMembership(models.Model):
title = models.CharField(max_length=100, blank=True)
order = models.IntegerField(
blank=True, null=True,
help_text="Higher numbers mean higher up in the food chain")
help_text="The lower the number the higher on the"
" page this Person will be shown.")
networkgroup = models.ForeignKey('NetworkGroup')
person = models.ForeignKey('Person')

Expand Down
2 changes: 0 additions & 2 deletions foundation/organisation/templates/organisation/member.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ <h4>Email:</h4>
{{ person.email }}
</div>
{% endif %}


</div>

<ul class="nav nav-tabs" role="tablist">
Expand Down
35 changes: 35 additions & 0 deletions foundation/organisation/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from unittest import TestCase

from ..utils import get_activity, extract_ograph_title


class HelpersTest(TestCase):
def test_get_activity_extracts_type(self):
text = '#reading http://goodreads.com/123'
self.assertEqual(get_activity(text), 'reading')

text2 = '#watching http://goodreads.com/123'
self.assertEqual(get_activity(text2), 'watching')

text3 = '#eating http://goodreads.com/123'
self.assertEqual(get_activity(text3), 'eating')

text4 = '#working http://goodreads.com/123'
self.assertEqual(get_activity(text4), 'working')

def test_get_activity_returns_none_when_no_match(self):
text = '#foobaring http://goodreads.com/123'
self.assertEqual(get_activity(text), None)

def test_extract_ograph_title(self):
link = 'http://www.goodreads.com/book/show/486625.Close_to_the_Machine'
text = '#reading {}'.format(link)
url, title = extract_ograph_title(text)
self.assertEqual(url, link)
self.assertEqual(title, 'Close to the Machine')

def test_extract_ograph_title_without_url(self):
text = '#working Making the world more open'
url, title = extract_ograph_title(text)
self.assertIsNone(url)
self.assertEqual(title, 'Making the world more open')
55 changes: 55 additions & 0 deletions foundation/organisation/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from mock import patch

from django.core.urlresolvers import reverse
from django.utils.html import escape
from django.test.utils import override_settings
Expand Down Expand Up @@ -653,3 +655,56 @@ def test_csv_output(self):
self.buckingham.twitter,
self.buckingham.youtube_url,
self.buckingham.facebook_url, 'Y', '' '']

@override_settings(HUBOT_API_KEY='secretkey')
def test_relatable_person_authentication(self):
response = self.app.post(reverse('relatable-person'),
status=403)
self.assertEqual(response.json['message'], "Not authorized")

@override_settings(HUBOT_API_KEY='secretkey')
def test_relatable_person_json_deocding(self):
response = self.app.post(reverse('relatable-person'),
headers={'Authorization': 'secretkey'},
status=400)
self.assertEqual(response.json['message'], "Could not decode JSON data.")

@override_settings(HUBOT_API_KEY='secretkey')
def test_relatable_person_without_username(self):
response = self.app.post_json(reverse('relatable-person'),
headers={'Authorization': 'secretkey'},
status=400,
params={"text": "#reading this should not happend"})
self.assertEqual("You need to supply a field `username`",
response.json['message'])

@override_settings(HUBOT_API_KEY='secretkey')
def test_relatable_person_username_not_found(self):
response = self.app.post_json(reverse('relatable-person'),
headers={'Authorization': 'secretkey'},
status=400,
params={"username": "knut"})
self.assertIn("No person with", response.json['message'])

@patch('foundation.organisation.views.extract_ograph_title',
return_value=('Close to the machine',
"https://www.goodreads.com/book/show/486625.Close_to_the_Machine")
)
@override_settings(HUBOT_API_KEY='secretkey')
def test_relatable_person_updates_entries(self, *args):
donatello = Person.objects.create(
name="Donatello (Donnie)",
username_on_slack="donnie",
description='Turtle with a purple mask',
email='donatello@tmnt.org')
donatello.save()
payload = {"username": "donnie",
"text": "#reading https://www.goodreads.com/book/show/486625.Close_to_the_Machine"
}

response = self.app.post_json(reverse('relatable-person'),
headers={'Authorization': 'secretkey'},
status=200,
params=payload)
donnie = Person.objects.filter(username_on_slack='donnie').first()
self.assertEqual(donnie.nowdoing_set.count(), 1)
34 changes: 34 additions & 0 deletions foundation/organisation/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import re
import opengraph

from django.http import JsonResponse

from foundation.organisation.models import NowDoing


def extract_ograph_title(text):
text_without_hashtag = ' '.join(text.split(' ')[1:])
url_pattern = r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]' \
+ '|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
urls = re.findall(url_pattern, text_without_hashtag)
if urls:
content = opengraph.OpenGraph(url=urls[0])
title = content.get('title', text_without_hashtag)
return urls[0], title.encode('utf-8')
return None, text_without_hashtag.encode('utf-8')


def get_activity(text):
options = '|'.join([a[0] for a in NowDoing.ACTIVITIES])
pattern = '^#({}).*'.format(options)
matches = re.findall(pattern, text)
if matches:
return matches[0]
return None


def fail_json(message, status_code=400):
response = JsonResponse({'success': False,
'message': message})
response.status_code = status_code
return response
49 changes: 47 additions & 2 deletions foundation/organisation/views.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import json

from django.views.decorators.cache import cache_page
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.list import ListView
from django.views.generic.detail import DetailView
from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.http import HttpResponse, JsonResponse
from django.conf import settings

from iso3166 import countries
import unicodecsv

from .models import (Board, Project, ProjectType, Theme, WorkingGroup,
NetworkGroup, NetworkGroupMembership)
NetworkGroup, NetworkGroupMembership, Person, NowDoing)

from utils import get_activity, fail_json, extract_ograph_title


class BoardView(DetailView):
Expand Down Expand Up @@ -111,6 +117,45 @@ def get_context_data(self, **kwargs):
return context


@csrf_exempt
def relatable_person(request):
auth = request.META.get('HTTP_AUTHORIZATION')

if auth != settings.HUBOT_API_KEY:
return fail_json('Not authorized', status_code=403)

try:
data = json.loads(request.body)
except ValueError:
return fail_json('Could not decode JSON data.')

username = data.get('username')
if not username:
return fail_json('You need to supply a field `username`')

person = Person.objects.filter(username_on_slack=username).first()
if not person:
message = 'No person with `username_on_slack` {}'.format(username)
return fail_json(message)

activity = get_activity(data.get('text'))

activities_of_this_type = person.nowdoing_set.filter(doing_type=activity)
for old_activity in activities_of_this_type:
old_activity.delete()

link, title = extract_ograph_title(data.get('text', ''))
now_doing = NowDoing(person=person,
doing_type=activity,
text=title,
link=link)
now_doing.save()

message = 'You are consuming: {}'.format(now_doing.text)
return JsonResponse({'success': True,
'message': message})


@cache_page(60 * 30)
def networkgroup_csv_output(request):
response = HttpResponse(content_type='text/csv')
Expand Down
1 change: 1 addition & 0 deletions foundation/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
BASE_DIR = os.path.dirname(os.path.dirname(__file__))

SITE_ID = int(env.get('DJANGO_SITE_ID', 1))
HUBOT_API_KEY = env.get('HUBOT_API_KEY')


def _parse_email_list(varname):
Expand Down
2 changes: 2 additions & 0 deletions foundation/tests/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.conf.urls import url, patterns, include
from foundation.organisation.views import relatable_person

from django.contrib import admin

Expand All @@ -19,5 +20,6 @@
url(r'^get-involved/working-groups',
include('foundation.organisation.urls.workinggroups')),
url(r'^network/', include('foundation.organisation.urls.networkgroups')),
url(r'^api$', relatable_person, name='relatable-person'),
url(r'^', include('cms.urls')),
)
8 changes: 8 additions & 0 deletions foundation/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from django.views.generic import RedirectView

from django.contrib import admin

from foundation.organisation.views import relatable_person

admin.autodiscover()

from haystack.views import SearchView
Expand Down Expand Up @@ -85,6 +88,11 @@
RedirectView.as_view(url=ARCHIVE_ROOT + '/wp-includes/%(remain)s',
permanent=True)),

# we would like to properly do this from within the cms
# but it does not work with `csrf_exempt`. See
# https://github.com/divio/django-cms/issues/4599
url(r'^api$', relatable_person, name='relatable-person'),

# Fallthrough for CMS managed pages
url(r'^', include('cms.urls'))
)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ iso3166==0.7
libsass==0.11.1
markdown2==2.3.1
newrelic==2.62.0.47
opengraph==0.5
psycopg2==2.6.1
elasticsearch==2.3.0
pylibmc==1.5.1
Expand Down

0 comments on commit 6e4257d

Please sign in to comment.